Skip to content

VNX-JWT-004: JWT algorithm explicitly set to 'none'

A JWT is signed with, or a verifier accepts, the 'none' algorithm: an algorithm/algorithms option naming 'none' or PyJWT algorithm=None (Python, JS/TS, Ruby), jose UnsecuredJWT, Auth0 Algorithm.none(), jjwt Jwts.parser().unsecured(), and golang-jwt SigningMethodNone or UnsafeAllowNoneSignatureType.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-JWT-004
Severity Critical
SARIF level error
Kind sast
Languages Python, JavaScript / TypeScript, Ruby, Java, Kotlin, Go
CWE CWE-327
CAPEC CAPEC-196
ATT&CK T1550.001
CWSS vector CWSS:1.0/TI:C/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:C/DI:H/EX:H/EC:N/P:W
Tags jwt algorithm authentication none-alg
Source internal/sast/rules/vnx-jwt-004.rego

Overview

This rule detects JWT operations that explicitly use or accept the none algorithm: an algorithm/algorithms option whose value is or contains 'none' (any case, including a list split over lines) or PyJWT jwt.encode(..., algorithm=None) in Python, JavaScript/TypeScript and Ruby files that use a JWT library; ruby-jwt JWT.encode(payload, nil, 'none'); jose new UnsecuredJWT() / UnsecuredJWT.decode(); Auth0 Algorithm.none() and jjwt Jwts.parser().unsecured(); and golang-jwt SigningMethodNone used to sign and UnsafeAllowNoneSignatureType. Code that compares a token’s alg with 'none' to reject it is not reported. The none algorithm is defined in RFC 7519 as an unsecured JWT: the signature section of the token is empty and no cryptographic verification is performed. Any party who holds such a token (or manufactures one from scratch) can present it as valid. Libraries that accept none as a permitted algorithm during verification allow an attacker to forge arbitrary token payloads and be accepted as authenticated. This maps to CWE-327 (Use of a Broken or Risky Cryptographic Algorithm).

The none algorithm was originally included in the JWT specification to support use cases where JWTs are transmitted over already-authenticated channels and a signature would be redundant. In practice, its inclusion in production authentication flows is almost always a mistake or a deliberate attack. Several high-profile CVEs have been filed against JWT libraries that accepted none by default or allowed it to be specified in the token header.

Why This Matters

The none algorithm attack on JWTs is well documented: an attacker takes a valid token, decodes the header (which is just base64), changes the alg field to "none", re-encodes, and strips the signature. A vulnerable library accepts this token as valid because it sees alg: none and skips signature verification. This allows complete authentication bypass and identity spoofing.

CAPEC-196 (Session Credential Falsification through Forging) and MITRE ATT&CK T1550.001 (Use Alternate Authentication Material: Application Access Token) document this attack. It requires only a base64 decoder and text editor, no cryptographic knowledge or computation. Any production system that accepts none-algorithm tokens effectively has no authentication at all.

The fix is straightforward: always specify a strong algorithm explicitly and never include none in the list of accepted algorithms.

What Gets Flagged

# FLAGGED: jwt.encode with algorithm='none'
token = jwt.encode({"sub": user_id}, key, algorithm="none")

# FLAGGED: jwt.decode accepting 'none' as the only algorithm
payload = jwt.decode(token, key, algorithms=["none"])
// FLAGGED: jwt.sign with algorithm: 'none'
const token = jwt.sign({ sub: userId }, secret, { algorithm: "none" });

// FLAGGED: verifier accepts 'none' alongside a real algorithm
const payload = jwt.verify(token, key, { algorithms: ["RS256", "none"] });
// FLAGGED
t := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{"sub": user})
return jwt.UnsafeAllowNoneSignatureType, nil
// FLAGGED
private final Algorithm algorithm = Algorithm.none();

Remediation

  1. Replace "none" with a strong signing algorithm. Use HS256 for symmetric (shared secret) use cases, RS256 or ES256 for asymmetric (public/private key) use cases.

    # SAFE: strong symmetric algorithm
    import jwt
    
    token = jwt.encode({"sub": user_id, "exp": expiry}, SECRET_KEY, algorithm="HS256")
    
    # SAFE: decode specifies only strong algorithms
    payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    // SAFE: strong algorithm in sign and verify
    const token = jwt.sign({ sub: userId }, secretKey, {
      algorithm: "HS256",
      expiresIn: "1h",
    });
    
    const payload = jwt.verify(token, secretKey, { algorithms: ["HS256"] });
  2. For asymmetric tokens (RS256, ES256), keep the private key out of the codebase and load it from a secrets manager or environment variable at runtime.

    // SAFE: RS256 with externally loaded private key
    const privateKey = process.env.JWT_PRIVATE_KEY;
    const token = jwt.sign({ sub: userId }, privateKey, { algorithm: "RS256" });
  3. Explicitly exclude "none" from the algorithms list in decode/verify calls even if not using it, to ensure the library cannot be tricked by a forged header.

    # SAFE: whitelist only specific algorithms, never include 'none'
    ALLOWED_ALGORITHMS = ["HS256", "HS384"]
    payload = jwt.decode(token, SECRET_KEY, algorithms=ALLOWED_ALGORITHMS)

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-jwt-004
// nosec vnx-jwt-004

Or record an ignore rule: vulnetix ignore add --rule vnx-jwt-004 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-jwt-004.rego, cli/internal/sast/docs/vnx-jwt-004.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.