What β OAuth login callbacks write their one-time authorization code (and state) into the HTTP access log in cleartext.
Why β Same single-use-secret-in-URL leak class already closed for other tokens; access logs persist and reach log shippers far longer than the code's short TTL.
Scope β lib/helpers/redactUrl.js SENSITIVE_QUERY_KEYS = ['inviteToken'] (~L9) does not cover code/state. GET /api/auth/:strategy/callback (modules/auth/routes/auth.routes.js) receives ?code=...&state=...; lib/services/express.js (~L204) mounts morgan.token('url', req => redactUrl(req.originalUrl)) unconditionally with no skip. So every OAuth callback logs code=/state= cleartext. Lower exploitability than a reset token (single-use, ~10min TTL, exchange needs client_secret) but a valid confirmed leak.
Fix: add 'code','state' to SENSITIVE_QUERY_KEYS (built-in defaults); test that a callback URL redacts them.
Refs: internal audit cycle-1 2026-07-16 (H3); same class as #3955/#3961.
Created via /dev:issue
What β OAuth login callbacks write their one-time authorization code (and state) into the HTTP access log in cleartext.
Why β Same single-use-secret-in-URL leak class already closed for other tokens; access logs persist and reach log shippers far longer than the code's short TTL.
Scope β
lib/helpers/redactUrl.jsSENSITIVE_QUERY_KEYS = ['inviteToken'](~L9) does not covercode/state.GET /api/auth/:strategy/callback(modules/auth/routes/auth.routes.js) receives?code=...&state=...;lib/services/express.js(~L204) mountsmorgan.token('url', req => redactUrl(req.originalUrl))unconditionally with noskip. So every OAuth callback logscode=/state=cleartext. Lower exploitability than a reset token (single-use, ~10min TTL, exchange needs client_secret) but a valid confirmed leak.Fix: add
'code','state'toSENSITIVE_QUERY_KEYS(built-in defaults); test that a callback URL redacts them.Refs: internal audit cycle-1 2026-07-16 (H3); same class as #3955/#3961.
Created via /dev:issue