Skip to content

VNX-294: Authentication Bypass by Capture-replay

Detects signed tokens accepted without their age being enforced: JWT expiry verification disabled (ignoreExpiration, verify_exp False), a clock leeway of a day or more, and itsdangerous/Django timestamped signatures loaded without max_age.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-294
Severity High
SARIF level error
Kind sast
Languages Java, JavaScript / TypeScript, Python
CWE CWE-294
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags auth replay token-expiry cwe-294
Source internal/sast/rules/vnx-294.rego

Overview

VNX-294 maps to CWE-294: Authentication Bypass by Capture-replay. A token that is never too old is a token that can be replayed: once it appears in a log, a proxy, browser history or a forwarded e-mail, whoever holds it is authenticated.

The rule reports:

  • Expiry verification switched off: jsonwebtoken { ignoreExpiration: true }; PyJWT/python-jose options={"verify_exp": False} (inline or in an options dict) and the old verify_expiration=False.
  • A leeway of a day or more: jsonwebtoken/jose clockTolerance, PyJWT leeway=, jjwt setAllowedClockSkewSeconds/clockSkewSeconds, Auth0 java-jwt acceptLeeway/acceptExpiresAt with a literal of 86400 or more, Long.MAX_VALUE or Integer.MAX_VALUE.
  • A timestamped signature read without a maximum age: itsdangerous URLSafeTimedSerializer/TimedSerializer/TimestampSigner .loads()/.unsign() and Django signing.loads()/TimestampSigner().unsign() without max_age.

What Gets Flagged

return jwt.verify(token, process.env.JWT_KEY, { ignoreExpiration: true });   // FLAGGED
serializer = URLSafeTimedSerializer(app.config["SECRET_KEY"])
email = serializer.loads(token, salt="password-reset")    # FLAGGED: never expires
.setAllowedClockSkewSeconds(Long.MAX_VALUE)               // FLAGGED

What Is Not Flagged

  • verify_exp: True, ignoreExpiration: false, and a leeway under a day.
  • loads(..., max_age=3600) and signing.loads(..., max_age=...).
  • Untimed serializers and signers, json.loads/pickle.loads, comments and string literals.

Tokens issued without an exp claim are reported by VNX-JWT-002. Nonce or jti replay caches are not judged.

Remediation

Keep expiry verification on and tokens short-lived, refresh them instead of accepting expired ones, and pass max_age to every timestamped loads()/unsign():

email = serializer.loads(token, salt="password-reset", max_age=3600)

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-294
// nosec vnx-294

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

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