CVE-2026-107719: fast-jwt Verifier Cache Accepts Expired JWTs Vulnerability

MODERATE
Advisory Identifier: GHSA-x937-hj6v-793p
CVSS 4.2
CWE-613
Published
Advisory Summary

CVE-2026-107719 is a MODERATE (CVSS 4.2) verifier cache accepts expired jwts vulnerability (CWE-613: Insufficient Session Expiration) affecting fast-jwt libraries in the npm ecosystem. The issue involves verifier cache accepts expired jwts without iat.. Remediation is available by upgrading affected packages to version 6.3.4.

CVE-2026-107719 at a Glance

CVE Identifier CVE-2026-107719
GHSA Identifier GHSA-x937-hj6v-793p
Severity MODERATE
CVSS v3.1 Base Score 4.2
CWE Classification CWE-613: Insufficient Session Expiration
Affected Product fast-jwt
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 4.2

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N).

Attack Vector Network (AV:N)
Attack Complexity High (AC:H)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity Low (I:L)
Availability None (A:N)

How to Fix CVE-2026-107719

To remediate this vulnerability, upgrade all affected fast-jwt dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

fast-jwt fixed: 6.3.4
npm install fast-jwt@6.3.4
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls fast-jwt

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-107719, inspect your project dependencies using npm:

npm ls fast-jwt

Analyze the terminal output to locate any version of fast-jwt in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 6.3.4), your application is affected.

Note: The npm ls fast-jwt command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-613: Insufficient Session Expiration, where according to wasc, "insufficient session expiration is when a web site permits an attacker to reuse old session credentials or session ids for authorization.". In fast-jwt, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary `cacheSet` only derives its `exp` cache deadline inside `hasIat` (`src/verifier.js:127-140`). JWT `iat` is optional. For a valid token with `exp` but no `iat`, `cacheSet` substitutes `clockTimestamp + clockTolerance + cacheTTL` (`src/verifier.js:142-146`). Later, the cache path returns the saved payload before decoding or invoking `verifyToken` (`src/verifier.js:372-388`). Since expiration validation is in `verifyToken`, the expired token remains accepted until the cache deadline. ### Details The bug is an expiration-check bypass caused by caching. Normally, verification works like this: 1. Verify signature. 2. Check exp against the current time. 3. Return the JWT claims. With caching enabled, `fast-jwt` saves successful verification results so it can avoid repeating crypto work for the same token. The intended invariant is: > A cached token must stop being accepted at the same time as a non-cached token. But the cache computes its expiration incorrectly. In `fast-jwt/src/verifier.js:127`, the code first checks whether the payload has an iat (“issued at”) claim: ```js const hasIat = payload && typeof payload.iat === 'number' if (hasIat) { if (!ignoreExpiration && typeof payload.exp === 'number') { cacheValue[2] = payload.exp * 1000 + clockTolerance } } ``` That means `exp` is considered only when `iat` exists. However, `iat` is optional in JWT. A perfectly valid JWT may contain: ```js { "sub": "alice", "exp": 1700000001 } ``` with no iat. For that token, the expiration-based cache deadline is never set. The code falls back to the generic cache TTL—10 minutes by default: ```js const maxTTL = clockTimestamp + clockTolerance + cacheTTL cacheValue[2] = cacheValue[2] === 0 ? maxTTL : Math.min(cacheValue[2], maxTTL) ``` Later, cache lookup happens before decoding and expiration validation: ```js const [value, min, max] = cache.get(cacheKeyBuilder(token)) || [undefined, 0, 0] if (typeof value !== 'undefined' && (max === 0 || now <= max)) { return handleCachedResult(value, callback, promise) } ``` So the timeline is: ``` 12:00:00 Token has exp = 12:00:01, no iat. 12:00:00 Server verifies it successfully and caches its claims. 12:00:01 Token expires. 12:00:02 Attacker replays the identical token. 12:00:02 Cache hit returns old claims; exp is never rechecked. 12:10:00 Cache TTL finally ends; normal expiration rejection resumes. ``` An attacker cannot forge a token through this issue. They need a valid token first; typically their own, or a stolen bearer token. But they can keep using it after its intended expiration if all of these are true: - The application enables `cache`. - The JWT has `exp` but no `iat`. - The token was cached before expiry. - It is replayed before the cache entry expires. Impact depends on the application. For a short-lived access token, it can extend access by up to the configured `cacheTTL` of 10 minutes by default, or more if the application configured it that way. This undermines expiry as an authentication/session boundary. The minimal repair is to calculate the cache deadline from `exp` independently of `iat`. `iat` should only matter for `maxAge`, because max age is inherently defined relative to issuance time. ### PoC ```js const assert = require('node:assert/strict') const { createSigner, createVerifier } = require('fast-jwt') const key = 'audit-only-secret' const originalNow = Date.now try { const initialNow = 1_700_000_000_000 const exp = Math.floor(initialNow / 1000) + 1 // expires one second later // noTimestamp intentionally omits iat, while exp is retained. const sign = createSigner({ key, algorithm: 'HS256', noTimestamp: true }) const token = sign({ sub: 'alice', exp }) const verify = createVerifier({ key, algorithms: ['HS256'], cache: true, cacheTTL: 60_000 }) Date.now = () => initialNow assert.equal(verify(token).sub, 'alice') // Valid; stores cache entry. Date.now = () => exp * 1000 + 1 assert.equal(verify(token).sub, 'alice') // BUG: should throw FAST_JWT_EXPIRED. console.log('VULNERABLE: expired exp-without-iat token served from cache') } finally { Date.now = originalNow } ``` ### Impact This is an authentication/session-expiration bypass caused by incorrect cache validation. The following are impacted: - Applications using the affected fast-jwt code with cache: true. - Applications that rely on JWT exp to end sessions or limit bearer-token lifetime. - Tokens with exp but no iat, after they were successfully verified and cached.

CVE-2026-107719 Affected fast-jwt Versions

1 component(s)
Fixed Versions:
6.3.4
Product / Package Ecosystem Affected Versions Fixed Version
fast-jwt
npm
< 6.3.4
6.3.4

References

Frequently Asked Questions

What is CVE-2026-107719?
CVE-2026-107719 is a security vulnerability in fast-jwt involving Verifier cache accepts expired JWTs without iat. under CWE-613: Insufficient Session Expiration.
Which fast-jwt versions are affected?
Affected versions include releases prior to 6.3.4 across affected packages: fast-jwt.
What version fixes CVE-2026-107719?
Upgrading to 6.3.4 (or the latest patched release for your respective branch) resolves the vulnerability.
Is fast-jwt affected?
Yes, fast-jwt is affected in releases prior to 6.3.4. Upgrade your dependency to 6.3.4 or later.
What is the CVSS score of CVE-2026-107719?
CVE-2026-107719 has a CVSS v3.1 base score of 4.2 (MODERATE) with vector string CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N.
What CWE is associated with CVE-2026-107719?
CVE-2026-107719 is classified under CWE-613: Insufficient Session Expiration (According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization.").
How can I check npm dependencies for CVE-2026-107719?
Run the dependency tree command: 'npm ls fast-jwt'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-x937-hj6v-793p",
  "modified" : "2026-10-08T22:10:35Z",
  "published" : "2026-10-08T22:10:35Z",
  "aliases" : [ "CVE-2026-107719" ],
  "summary" : "fast-jwt: Verifier cache accepts expired JWTs without iat.",
  "details" : "### Summary\n`cacheSet` only derives its `exp` cache deadline inside `hasIat` (`src/verifier.js:127-140`). JWT `iat` is optional. For a valid token with `exp` but no `iat`, `cacheSet` substitutes `clockTimestamp + clockTolerance + cacheTTL` (`src/verifier.js:142-146`). Later, the cache path returns the saved payload before decoding or invoking `verifyToken` (`src/verifier.js:372-388`). Since expiration validation is in `verifyToken`, the expired token remains accepted until the cache deadline.\n\n### Details\nThe bug is an expiration-check bypass caused by caching.\n\nNormally, verification works like this:\n\n  1. Verify signature.\n  2. Check exp against the current time.\n  3. Return the JWT claims.\n\nWith caching enabled, `fast-jwt` saves successful verification results so it can avoid repeating crypto work for the same token.\n\nThe intended invariant is:\n\n  > A cached token must stop being accepted at the same time as a non-cached token.\n\nBut the cache computes its expiration incorrectly.\n\nIn `fast-jwt/src/verifier.js:127`, the code first checks whether the payload has an iat (“issued at”) claim:\n\n```js\nconst hasIat = payload && typeof payload.iat === 'number'\n\nif (hasIat) {\n  if (!ignoreExpiration && typeof payload.exp === 'number') {\n    cacheValue[2] = payload.exp * 1000 + clockTolerance\n  }\n}\n```\n\nThat means `exp` is considered only when `iat` exists.\n\nHowever, `iat` is optional in JWT. A perfectly valid JWT may contain:\n\n```js\n{\n    \"sub\": \"alice\",\n    \"exp\": 1700000001\n}\n```\nwith no iat.\n\nFor that token, the expiration-based cache deadline is never set. The code falls back to the generic cache TTL—10 minutes by default:\n\n```js\nconst maxTTL = clockTimestamp + clockTolerance + cacheTTL\ncacheValue[2] = cacheValue[2] === 0 ? maxTTL : Math.min(cacheValue[2], maxTTL)\n```\n\nLater, cache lookup happens before decoding and expiration validation:\n\n```js\nconst [value, min, max] = cache.get(cacheKeyBuilder(token)) || [undefined, 0, 0]\n\nif (typeof value !== 'undefined' && (max === 0 || now <= max)) {\n  return handleCachedResult(value, callback, promise)\n}\n```\n\nSo the timeline is:\n\n```\n  12:00:00  Token has exp = 12:00:01, no iat.\n  12:00:00  Server verifies it successfully and caches its claims.\n  12:00:01  Token expires.\n  12:00:02  Attacker replays the identical token.\n  12:00:02  Cache hit returns old claims; exp is never rechecked.\n  12:10:00  Cache TTL finally ends; normal expiration rejection resumes.\n```\n\nAn attacker cannot forge a token through this issue. They need a valid token first; typically their own, or a stolen bearer token. But they can keep using it after its intended expiration if all of these are true:\n\n  - The application enables `cache`.\n  - The JWT has `exp` but no `iat`.\n  - The token was cached before expiry.\n  - It is replayed before the cache entry expires.\n\nImpact depends on the application. For a short-lived access token, it can extend access by up to the configured `cacheTTL` of 10 minutes by default, or more if the application configured it that way. This undermines expiry as an authentication/session boundary.\n\nThe minimal repair is to calculate the cache deadline from `exp` independently of `iat`. `iat` should only matter for `maxAge`, because max age is inherently defined relative to issuance time.\n\n### PoC\n```js\nconst assert = require('node:assert/strict')\nconst { createSigner, createVerifier } = require('fast-jwt')\n\nconst key = 'audit-only-secret'\nconst originalNow = Date.now\n\ntry {\n  const initialNow = 1_700_000_000_000\n  const exp = Math.floor(initialNow / 1000) + 1 // expires one second later\n\n  // noTimestamp intentionally omits iat, while exp is retained.\n  const sign = createSigner({\n    key,\n    algorithm: 'HS256',\n    noTimestamp: true\n  })\n  const token = sign({ sub: 'alice', exp })\n\n  const verify = createVerifier({\n    key,\n    algorithms: ['HS256'],\n    cache: true,\n    cacheTTL: 60_000\n  })\n\n  Date.now = () => initialNow\n  assert.equal(verify(token).sub, 'alice') // Valid; stores cache entry.\n\n  Date.now = () => exp * 1000 + 1\n  assert.equal(verify(token).sub, 'alice') // BUG: should throw FAST_JWT_EXPIRED.\n\n  console.log('VULNERABLE: expired exp-without-iat token served from cache')\n} finally {\n  Date.now = originalNow\n}\n```\n\n### Impact\nThis is an authentication/session-expiration bypass caused by incorrect cache validation.\nThe following are impacted:\n  - Applications using the affected fast-jwt code with cache: true.\n  - Applications that rely on JWT exp to end sessions or limit bearer-token lifetime.\n  - Tokens with exp but no iat, after they were successfully verified and cached.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "fast-jwt"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "6.3.4"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 6.3.3"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/security/advisories/GHSA-x937-hj6v-793p"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/commit/fc1ddbbe5ce38066ba2cea0b0ce0233932757167"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/nearform/fast-jwt"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/nearform/fast-jwt/releases/tag/v6.3.4"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-613" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-08T22:10:35Z",
    "nvd_published_at" : null
  }
}