Bug: clockTolerance accepts arbitrarily large values, bypassing exp verification entirely
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Quiet
- Tech stack
- javascript, node.js
- Domain
- authentication, backend, security
Research direction
Start in verify.js, tracing clockTolerance validation and the exp/nbf comparisons. Confirm the supported upper bound and error behavior with maintainers, then add focused regression coverage for oversized tolerance and both checks. Done means large values are rejected without weakening normal clock-skew handling.
Written by the indexing model from the issue text.
Description
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
jsonwebtokenversion: 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
clockTolerancefrom 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
- Dominant language
- JavaScript
- Stars
- 18.2k
- Forks
- 1.3k
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from auth0/node-jsonwebtoken
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
auth0/node-jsonwebtoken#1042 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
auth0/node-jsonwebtoken#1000 · 2 comments · 1 reaction ·
-
Difficulty 4/5 3-5 days Newbie friendliness 65/100
auth0/node-jsonwebtoken#1046 ·
-
Difficulty 5/5 Over a week Newbie friendliness 10/100
auth0/node-jsonwebtoken#1034 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 48/100
auth0/node-jsonwebtoken#1032 ·
All issues in auth0/node-jsonwebtoken
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
mksglu/context-mode#1200 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
neondatabase/website#5944 ·
-
module: core
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
bigbluebutton/bigbluebutton#25849 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
jaegertracing/jaeger-ui#4506 ·