JWT Algorithm Confusion (alg:none & RS→HS)
debt(d5/e3/b3/t9)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as the tools, and the code_pattern describes conditions like 'JWT verification without specifying allowed algorithms' — these are patterns a SAST tool (semgrep) or static analyser (psalm) can flag, but a standard linter won't catch them by default. The flaw is invisible at runtime in normal operation until an attacker exploits it.
Closest to 'simple parameterised fix' (e3). The quick_fix says 'always specify an allowlist of permitted algorithms when verifying JWTs' — this is a targeted configuration change or a small code update at the verification call site, not a one-liner swap but also not a multi-file refactor. Switching to a well-maintained library like firebase/php-jwt and passing the algorithm allowlist parameter is contained within the JWT verification logic.
Closest to 'localised tax' (b3). The applies_to scope is web and API contexts, and the fix is confined to JWT verification points in the codebase. While authentication is load-bearing, the algorithmic allowlist fix is typically concentrated in one or a few verification helpers rather than spread across every file — it does not define the system's shape or impose a persistent productivity tax on unrelated work streams.
Closest to 'catastrophic trap' (t9). The misconception field states explicitly that developers believe 'any JWT library handles the alg:none attack automatically' — this is exactly backwards. Older and poorly maintained libraries actively accept attacker-controlled algorithm values unless explicitly restricted. The RS256→HS256 confusion is an additional catastrophic inversion where the public key becomes the HMAC secret. The 'obvious' default behaviour (trust the library, trust the token header) is always wrong in this context.
Also Known As
TL;DR
Explanation
Two classic JWT attacks exploit algorithm flexibility. The alg:none attack: some libraries accept a token with {'alg':'none'} and no signature, treating it as valid — an attacker can forge any payload. The algorithm confusion attack: an RS256 token (asymmetric — signed with private key, verified with public key) is downgraded to HS256 (symmetric HMAC). If the library reuses the public key as the HMAC secret, an attacker who knows the public key can sign arbitrary tokens. Defences: always specify the expected algorithm explicitly in the verify call; never accept algorithm from the token header; use a dedicated JWT library (firebase/php-jwt, lcobucci/jwt) that enforces algorithm allowlisting; reject tokens with alg:none unconditionally.
Common Misconception
Why It Matters
Common Mistakes
- Using a JWT library that accepts whatever algorithm is specified in the token header without validating against an allowlist.
- Not explicitly specifying the expected algorithm when verifying — letting the token dictate its own verification.
- Mixing HS256 and RS256 — sending an HS256 token to a server expecting RS256 can cause the public key to be used as the HMAC secret.
- Trusting the decoded payload before verifying the signature — some libraries return data even on verification failure.
Code Examples
JWT::decode($token, $key); // accepts whatever alg the token claims
JWT::decode($token, new Key($key, 'RS256')); // algorithm pinned explicitly