v0.15 is out: rules that were silently dropped on the shipped engine now run, and scans skip your test and mock files by default. Read more →
MEDIUM AI PREVALENCE: MEDIUM auth.py.jwt.pyjwt-no-audience

PyJWT jwt.decode(...) verifies the signature (an algorithms list is given) but passes no audience, so the aud claim is not checked.

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

PyJWT validates aud only when you pass audience=, so a token minted for a different service that trusts the same issuer or key is accepted here: a confused deputy (CWE-345). For an MCP or OAuth 2.1 resource server this is the RFC 9068 / RFC 8707 audience binding the spec requires.

Pass the audience you expect (your resource identifier): jwt.decode(token, key, algorithms=["RS256"], audience="https://mcp.example.com/mcp")

VULNERABLE
vulnerable.py
import jwt


def verify(token: str, key: str):
    # ruleid: auth.py.jwt.pyjwt-no-audience
    return jwt.decode(token, key, algorithms=["RS256"], issuer="https://auth.example.com")
SAFE
safe.py
import jwt


def verify(token: str, key: str):
    # ok: auth.py.jwt.pyjwt-no-audience
    return jwt.decode(
        token,
        key,
        algorithms=["RS256"],
        issuer="https://auth.example.com",
        audience="https://mcp.example.com/mcp",
    )

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.jwt.pyjwt-no-audience -- <reason>

References

https://datatracker.ietf.org/doc/html/rfc9068 ↗https://datatracker.ietf.org/doc/html/rfc8707 ↗https://pyjwt.readthedocs.io/en/stable/api.html#jwt.decode ↗