Severity by source
AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
Log and APM systems are network-accessible (AV:N); exploiting them requires existing log-read privilege (PR:L) and is not trivially achievable (AC:H); only confidentiality is impacted.
Primary rating from Vendor (https://github.com/jleehr/canto-saas-api).
CVSS VectorVendor: https://github.com/jleehr/canto-saas-api
Lifecycle Timeline
2DescriptionCVE.org
Summary
In affected versions, the OAuth2 token request sends app_id, app_secret, refresh_token and code as URL query parameters of the POST request to https://oauth.<domain>/oauth/api/oauth2/token. Request URLs are commonly recorded in access logs, proxy logs and APM traces, so the application secret and refresh token can be persisted in plain text outside the application's control.
In addition, when the token request fails, the Guzzle exception message - which contains the full request URI including the credentials - was passed unmodified into the AuthorizationFailedException thrown by OAuth2::obtainAccessToken(). Applications that log exceptions or forward them to error trackers (e.g. Sentry) may therefore have recorded the app secret in their logs.
Impact
An attacker with access to web server logs, proxy logs, APM tracing data or application error logs of a consumer of this library can obtain the Canto app_secret, refresh_token or authorization code and use them to obtain access tokens for the Canto tenant.
Patches
Fixed in 3.0.0:
- OAuth credentials are sent in the form-encoded POST body instead of the URL
query string (RFC 6749 §2.3.1). OAuth2Request::getQueryParams() now returns null; the parameters are available via getFormParams().
- Exception messages are sanitized before being rethrown: the values of
app_secret, refresh_token and code are masked (including url-encoded, differently cased and JSON-embedded variants).
Workarounds
If you cannot upgrade:
- Treat web server, proxy and APM logs of systems performing Canto OAuth
requests as secret material and restrict access to them.
- Catch
AuthorizationFailedExceptionin your application and strip the query
string from the message before logging or forwarding it.
If your logs may have been exposed, rotate the affected Canto app secret.
AnalysisAI
OAuth2 credential exposure in the jleehr/canto-saas-api PHP Composer library (versions ≤ 2.0.0) allows any party with read access to web server logs, proxy logs, APM traces, or error tracking platforms to harvest Canto app_secret, refresh_token, and authorization code values in plaintext. The library violates RFC 6749 §2.3.1 by transmitting these credentials as URL query parameters on POST requests to the Canto OAuth token endpoint, rather than in the form-encoded POST body - causing them to be recorded by default in virtually every HTTP logging layer. A second exposure path exists through failed token requests: Guzzle exception messages containing the full credential-laden URI were propagated unmodified into AuthorizationFailedException, meaning error trackers such as Sentry may also have captured these secrets. No public exploit has been identified at time of analysis, and the vulnerability is not listed in CISA KEV.
Technical ContextAI
The affected package is the Composer PHP library jleehr/canto-saas-api (CPE: pkg:composer/jleehr_canto-saas-api), which provides OAuth2 integration with the Canto digital asset management SaaS platform. The root cause maps to CWE-209 (Generation of Error Message Containing Sensitive Information), though the primary design flaw is the broader violation of RFC 6749 §2.3.1, which mandates that client credentials be transmitted in the HTTP request body as application/x-www-form-urlencoded, not in the URL query string. The library uses Guzzle as its HTTP client; on request failure, Guzzle constructs exception messages that embed the full request URI. The OAuth2::obtainAccessToken() method previously caught these exceptions and re-raised them as AuthorizationFailedException without sanitizing the message, propagating the credential-bearing URI up to any application-level exception handler or logging framework. URLs are logged by default in nginx, Apache, AWS ALB, Cloudflare, Datadog APM, New Relic, and Sentry, making credential persistence in log storage a near-certain outcome for any deployment using this library.
RemediationAI
The primary remediation is to upgrade jleehr/canto-saas-api to version 3.0.0 or later via Composer (composer require jleehr/canto-saas-api:^3.0.0). Version 3.0.0 moves all OAuth credentials into the form-encoded POST body per RFC 6749 §2.3.1 and masks sensitive parameter values in exception messages before rethrowing. Note the breaking API change: OAuth2Request::getQueryParams() now returns null; any application code inspecting OAuth request parameters must be updated to call getFormParams() instead. The advisory is at https://github.com/jleehr/canto-saas-api/security/advisories/GHSA-37pm-83g7-r22v. If immediate upgrade is not feasible, restrict read access to web server access logs, reverse proxy logs, and APM tracing data for all systems performing Canto OAuth requests - treating these as secret material. Additionally, wrap all calls to OAuth2::obtainAccessToken() in try/catch blocks and strip the query string from caught AuthorizationFailedException messages before forwarding to loggers or error trackers; this prevents secondary Sentry/APM exposure but does not stop credential leakage into access logs. If there is any reason to believe logs have been accessed by unauthorized parties, immediately rotate the Canto app secret regardless of upgrade status.
Same weakness CWE-209 – Error Message Information Leak
View allSame technique Information Disclosure
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-78902
GHSA-37pm-83g7-r22v