v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: MEDIUM auth.py.flow.oauth-credential-in-log

An OAuth/OIDC credential from the request flows into a logging call.

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 print, logging.*, or a 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, log a non-sensitive identifier instead (a user id, a key id), or drop the field entirely.

VULNERABLE
vulnerable.py
"""Flask views that leak OAuth credentials from the request into logs."""

import logging

from flask import Flask, request

app = Flask(__name__)
logger = logging.getLogger(__name__)


@app.route("/callback")
def callback():
    # Authorization code from the callback printed directly.
    # ruleid: auth.py.flow.oauth-credential-in-log
    print(request.args.get("code"))
    return "ok"


@app.route("/exchange")
def exchange():
    # access_token assigned to a local, then logged (indirection).
    access_token = request.args.get("access_token")
    # ruleid: auth.py.flow.oauth-credential-in-log
    logger.info("token exchange complete: %s", access_token)
    return "ok"


@app.route("/introspect")
def introspect():
    # Raw Authorization header logged on failure.
    # ruleid: auth.py.flow.oauth-credential-in-log
    logging.error("auth failed for %s", request.headers.get("Authorization"))
    return "ok"


@app.route("/refresh")
def refresh():
    # refresh_token from the form body logged via logger.debug.
    refresh_token = request.form["refresh_token"]
    # ruleid: auth.py.flow.oauth-credential-in-log
    logger.debug(refresh_token)
    return "ok"
SAFE
safe.py
"""Flask views that log safely — no OAuth credential reaches a log sink."""

import logging

from flask import Flask, request

app = Flask(__name__)
logger = logging.getLogger(__name__)


def redact(value):
    """Return only a short, non-sensitive prefix of a credential."""
    return (value[:4] + "...") if value else value


@app.route("/ping")
def ping():
    # Constant status message — no request data, no taint.
    print("oauth callback received")
    return "ok"


@app.route("/list")
def list_items():
    # Benign request field (pagination) — not a credential source.
    logger.info("listing page %s", request.args.get("page"))
    return "ok"


@app.route("/callback")
def callback():
    # Sanitized: the authorization code is redacted before logging.
    code = request.args.get("code")
    logger.info("code prefix %s", redact(code))
    return "ok"

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.

# oauthlint-disable-next-line auth.py.flow.oauth-credential-in-log -- <reason>

References

https://cwe.mitre.org/data/definitions/532.html ↗https://datatracker.ietf.org/doc/html/rfc6749#section-10.3 ↗https://owasp.org/API-Security/editions/2023/en/0xa8-security-misconfiguration/ ↗