Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.
Why this matters
The tainted value is an authorization code, an access_token / refresh_token / id_token, a bearer token, a client_secret, or the raw Authorization header, and the sink is a console.* or logger.* call. Logs are written to files, shipped to aggregators (Datadog, Splunk, CloudWatch) and read by people and systems that should never see live credentials. A leaked authorization code or token can be replayed to impersonate the user or complete the OAuth exchange (CWE-532).
Never log the raw credential. Redact or mask it before logging (token.slice(0, 4) + '…'), log a non-sensitive identifier instead (a user id, a key id), or drop the field entirely.
VULNERABLE
vulnerable.ts
import type { Request, Response } from 'express';declare const logger: { info: (...a: unknown[]) => void; debug: (...a: unknown[]) => void; error: (...a: unknown[]) => void;};// Authorization code from the callback logged directly.export function callback(req: Request, res: Response): void { // ruleid: auth.flow.oauth-credential-in-log console.log('oauth callback', req.query.code); res.sendStatus(200);}// access_token assigned to a local, then logged via a logger (indirection).export function exchange(req: Request, res: Response): void { const at = req.body.access_token as string; // ruleid: auth.flow.oauth-credential-in-log logger.info('token exchange complete', at); res.sendStatus(200);}// Raw Authorization header logged on an auth failure.export function authFailure(req: Request, res: Response): void { // ruleid: auth.flow.oauth-credential-in-log console.error('auth failed for', req.headers.authorization); res.sendStatus(401);}// refresh_token from the body logged via logger.debug.export function refresh(req: Request, res: Response): void { const rt = req.body.refresh_token as string; // ruleid: auth.flow.oauth-credential-in-log logger.debug(rt); res.sendStatus(200);}// id_token via index accessor, interpolated into a warning.export function verifyId(req: Request, res: Response): void { // ruleid: auth.flow.oauth-credential-in-log console.warn('received id_token', req.query['id_token']); res.sendStatus(200);}
SAFE
safe.ts
import type { Request, Response } from 'express';declare const logger: { info: (...a: unknown[]) => void; debug: (...a: unknown[]) => void;};declare function redact(value: string): string;// Constant status message — no request data, no taint.export function ping(_req: Request, res: Response): void { console.log('oauth callback received'); res.sendStatus(200);}// Benign request field (pagination) — not a credential source.export function list(req: Request, res: Response): void { console.log('listing page', req.query.page); res.sendStatus(200);}// Non-credential identifier — safe to log.export function whoami(req: Request, res: Response): void { logger.info('request from user', req.query.userId); res.sendStatus(200);}// Sanitized: only a masked prefix of the code is logged.export function callback(req: Request, res: Response): void { const code = req.query.code as string; console.log('code prefix', code.slice(0, 6)); res.sendStatus(200);}// Sanitized: the Authorization header is redacted before logging.export function authFailure(req: Request, res: Response): void { logger.debug('auth failed for', redact(req.headers.authorization as string)); res.sendStatus(401);}
Suppressing this rule
If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.