CVE-2025-30144
Description
fast-jwt provides fast JSON Web Token (JWT) implementation. Prior to 5.0.6, the fast-jwt library does not properly validate the iss claim based on the RFC 7519. 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. This issue is fixed in 5.0.6.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
fast-jwtnpm | < 5.0.6 | 5.0.6 |
Patches
20dbaa53023bacc26b1d473f9Merge commit from fork
2 files changed · +152 −0
src/verifier.js+12 −0 modified@@ -152,6 +152,16 @@ function validateClaimType(values, claim, array, type) { } function validateClaimValues(values, claim, allowed, arrayValue) { + const failureMessage = arrayValue + ? `Not all of the ${claim} claim values are allowed.` + : `The ${claim} claim value is not allowed.` + + if (!values.every(v => allowed.some(a => a.test(v)))) { + throw new TokenError(TokenError.codes.invalidClaimValue, failureMessage) + } +} + +function validateClaimArrayValues(values, claim, allowed, arrayValue) { const failureMessage = arrayValue ? `None of ${claim} claim values are allowed.` : `The ${claim} claim value is not allowed.` @@ -222,6 +232,8 @@ function verifyToken( if (type === 'date') { validateClaimDateValue(value, modifier, now, greater, errorCode, errorVerb) + } else if (array) { + validateClaimArrayValues(values, claim, allowed, arrayValue) } else { validateClaimValues(values, claim, allowed, arrayValue) }
test/verifier.spec.js+140 −0 modified@@ -500,6 +500,41 @@ test('it validates the jti claim only if explicitily enabled', t => { { message: 'The jti claim value is not allowed.' } ) + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOlsiSlRJIiwiSlRJMSJdLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.H2GACKIYvauUswRaK3SVsSwUOTjEcQDb1Qj_iCuLWoM', + { allowedJti: ['JTI'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the jti claim values are allowed.' } + ) + + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOlsiSlRJIiwiSlRJMSJdLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.H2GACKIYvauUswRaK3SVsSwUOTjEcQDb1Qj_iCuLWoM', + { allowedJti: ['JTI', 'JTI2'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the jti claim values are allowed.' } + ) + + t.assert.deepStrictEqual( + verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOlsiSlRJIiwiSlRJMSJdLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.H2GACKIYvauUswRaK3SVsSwUOTjEcQDb1Qj_iCuLWoM', + { allowedJti: ['JTI', 'JTI1'], key: 'secret-secret-secret-secret-secret' } + ), + { + a: 1, + jti: ['JTI', 'JTI1'], + aud: ['AUD1'], + iss: 'ISS', + sub: 'SUB', + nonce: 'NONCE' + } + ) + t.assert.deepStrictEqual( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', @@ -664,6 +699,41 @@ test('it validates the iss claim only if explicitily enabled', t => { { message: 'The iss claim value is not allowed.' } ) + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOlsiSVNTIiwiSVNTMSJdLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.IS9XILuqYEAKycN8j2MT0121j19T02CbW_h0erVh5IE', + { allowedIss: ['ISS'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the iss claim values are allowed.' } + ) + + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOlsiSVNTIiwiSVNTMSJdLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.IS9XILuqYEAKycN8j2MT0121j19T02CbW_h0erVh5IE', + { allowedIss: ['ISS', 'ISS2'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the iss claim values are allowed.' } + ) + + t.assert.deepStrictEqual( + verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOlsiSVNTIiwiSVNTMSJdLCJzdWIiOiJTVUIiLCJub25jZSI6Ik5PTkNFIn0.IS9XILuqYEAKycN8j2MT0121j19T02CbW_h0erVh5IE', + { allowedIss: ['ISS', 'ISS1'], key: 'secret-secret-secret-secret-secret' } + ), + { + a: 1, + jti: 'JTI', + aud: ['AUD1'], + iss: ['ISS', 'ISS1'], + sub: 'SUB', + nonce: 'NONCE' + } + ) + t.assert.deepStrictEqual( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', @@ -741,6 +811,41 @@ test('it validates the sub claim only if explicitily enabled', t => { { message: 'The sub claim value is not allowed.' } ) + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOlsiU1VCMSIsIlNVQjIiXSwibm9uY2UiOiJOT05DRSJ9.RwBpdTCEFCxO0jIFPnJpxjRd0JVIhP2Eettmsh0uwzY', + { allowedSub: ['SUB1'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the sub claim values are allowed.' } + ) + + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOlsiU1VCMSIsIlNVQjIiXSwibm9uY2UiOiJOT05DRSJ9.RwBpdTCEFCxO0jIFPnJpxjRd0JVIhP2Eettmsh0uwzY', + { allowedSub: ['SUB1', 'SUB3'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the sub claim values are allowed.' } + ) + + t.assert.deepStrictEqual( + verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOlsiU1VCMSIsIlNVQjIiXSwibm9uY2UiOiJOT05DRSJ9.RwBpdTCEFCxO0jIFPnJpxjRd0JVIhP2Eettmsh0uwzY', + { allowedSub: ['SUB1', 'SUB2'], key: 'secret-secret-secret-secret-secret' } + ), + { + a: 1, + jti: 'JTI', + aud: ['AUD1'], + iss: 'ISS', + sub: ['SUB1', 'SUB2'], + nonce: 'NONCE' + } + ) + t.assert.deepStrictEqual( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw', @@ -818,6 +923,41 @@ test('it validates the nonce claim only if explicitily enabled', t => { { message: 'The nonce claim value is not allowed.' } ) + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6WyJOT05DRSIsIk5PTkNFMSJdfQ.a8ZSzXebJvaw32jyWgbBo9aeLNTgs_sqxD2llV4f8KQ', + { allowedNonce: ['NONCE'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the nonce claim values are allowed.' } + ) + + t.assert.throws( + () => { + return verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6WyJOT05DRSIsIk5PTkNFMSJdfQ.a8ZSzXebJvaw32jyWgbBo9aeLNTgs_sqxD2llV4f8KQ', + { allowedNonce: ['NONCE', 'NONCE2'], key: 'secret-secret-secret-secret-secret' } + ) + }, + { message: 'Not all of the nonce claim values are allowed.' } + ) + + t.assert.deepStrictEqual( + verify( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSJdLCJpc3MiOiJJU1MiLCJzdWIiOiJTVUIiLCJub25jZSI6WyJOT05DRSIsIk5PTkNFMSJdfQ.a8ZSzXebJvaw32jyWgbBo9aeLNTgs_sqxD2llV4f8KQ', + { allowedNonce: ['NONCE', 'NONCE1'], key: 'secret-secret-secret-secret-secret' } + ), + { + a: 1, + jti: 'JTI', + aud: ['AUD1'], + iss: 'ISS', + sub: 'SUB', + nonce: ['NONCE', 'NONCE1'] + } + ) + t.assert.deepStrictEqual( verify( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoxLCJqdGkiOiJKVEkiLCJhdWQiOlsiQVVEMSIsIkRVQTIiXSwiaXNzIjoiSVNTIiwic3ViIjoiU1VCIiwibm9uY2UiOiJOT05DRSJ9.8fqzi23J-GjaD7rW3OYJv8UtBYkx8MOkViJjS4sXmVw',
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-gm45-q3v2-6cf8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-30144ghsaADVISORY
- datatracker.ietf.org/doc/html/rfc7519nvdWEB
- github.com/nearform/fast-jwt/commit/cc26b1d473f900446ad846f8f0b10eb1c0adcbddnvdWEB
- github.com/nearform/fast-jwt/security/advisories/GHSA-gm45-q3v2-6cf8nvdWEB
News mentions
0No linked articles in our index yet.