Summary
The clockTolerance option in verify() accepts any positive integer with no upper bound validation. Passing Number.MAX_SAFE_INTEGER (or any value large enough that exp + clockTolerance overflows to Infinity) causes the expiry check to silently pass for any expired token, regardless of how long ago it expired.
Environment
jsonwebtoken version: 9.0.3 (latest)
- Node.js: v20+
Reproduction
const jwt = require("jsonwebtoken");
const SECRET = "supersecret";
// Sign a token that expired 1 year ago
const expiredToken = jwt.sign(
{ sub: "user", role: "admin" },
SECRET,
{ expiresIn: "-365d" }
);
// Normal verify correctly rejects it
try {
jwt.verify(expiredToken, SECRET);
} catch (e) {
console.log(e.message); // "jwt expired"
}
// Bypass with MAX_SAFE_INTEGER clockTolerance
const payload = jwt.verify(expiredToken, SECRET, {
clockTolerance: Number.MAX_SAFE_INTEGER // 9007199254740991
});
console.log(payload); // { sub: "user", role: "admin", ... } — token accepted!
Root Cause
In verify.js, the expiry check is:
if (clockTimestamp >= payload.exp + (options.clockTolerance || 0)) {
return done(new TokenExpiredError(...));
}
When clockTolerance is Number.MAX_SAFE_INTEGER, the addition payload.exp + 9007199254740991 produces a value far larger than any realistic clockTimestamp. The comparison becomes:
1775002429 >= 9007200998207420 → false
So the expiry check is skipped entirely. The same issue affects the nbf (not before) check via the same pattern.
No validation is performed on clockTolerance beyond checking it is a number:
// Current validation (insufficient):
if (options.clockTimestamp && typeof options.clockTimestamp !== "number") {
return done(new JsonWebTokenError("clockTimestamp must be a number"));
}
// clockTolerance has NO validation at all
Impact
Any application that:
- Reads
clockTolerance from user input, a config file, environment variable, or a database without strict validation, OR
- Has a dependency that passes an unvalidated
clockTolerance
...is vulnerable to complete expiry bypass. An attacker who can influence the clockTolerance value can reuse tokens that expired days, months, or years ago.
This is particularly dangerous in multi-tenant systems where token verification options may be partially user-controlled.
Suggested Fix
Add an upper bound to clockTolerance. A reasonable maximum is 300 seconds (5 minutes) or at most 86400 (1 day). For example:
if (options.clockTolerance !== undefined) {
if (typeof options.clockTolerance !== "number" || options.clockTolerance < 0) {
return done(new JsonWebTokenError("clockTolerance must be a non-negative number"));
}
if (options.clockTolerance > 300) {
return done(new JsonWebTokenError("clockTolerance must not exceed 300 seconds"));
}
}
Alternatively, document clearly that clockTolerance must be a small value and add a warning when it exceeds a reasonable threshold.
Discovered via manual source code audit of v9.0.3.
Reported by Travis Burmaster — travis@burmaster.com
Summary
The
clockToleranceoption inverify()accepts any positive integer with no upper bound validation. PassingNumber.MAX_SAFE_INTEGER(or any value large enough thatexp + clockToleranceoverflows toInfinity) causes the expiry check to silently pass for any expired token, regardless of how long ago it expired.Environment
jsonwebtokenversion: 9.0.3 (latest)Reproduction
Root Cause
In
verify.js, the expiry check is:When
clockToleranceisNumber.MAX_SAFE_INTEGER, the additionpayload.exp + 9007199254740991produces a value far larger than any realisticclockTimestamp. The comparison becomes:So the expiry check is skipped entirely. The same issue affects the
nbf(not before) check via the same pattern.No validation is performed on
clockTolerancebeyond checking it is a number:Impact
Any application that:
clockTolerancefrom user input, a config file, environment variable, or a database without strict validation, ORclockTolerance...is vulnerable to complete expiry bypass. An attacker who can influence the
clockTolerancevalue can reuse tokens that expired days, months, or years ago.This is particularly dangerous in multi-tenant systems where token verification options may be partially user-controlled.
Suggested Fix
Add an upper bound to
clockTolerance. A reasonable maximum is300seconds (5 minutes) or at most86400(1 day). For example:Alternatively, document clearly that
clockTolerancemust be a small value and add a warning when it exceeds a reasonable threshold.Discovered via manual source code audit of v9.0.3.
Reported by Travis Burmaster — travis@burmaster.com