require-algorithm-whitelist
This rule enforces explicit algorithm specification in verify() calls
Require explicit algorithm specification in JWT verify operations
Severity: 🟠 High
CWE: CWE-757
Rule Details
This rule enforces explicit algorithm specification in verify() calls. Without explicit algorithms, the token's header algorithm is trusted, enabling algorithm substitution attacks.
Examples
❌ Incorrect
// No algorithms specified - trusts token header
jwt.verify(token, secret);
jwt.verify(token, secret, {});
jwt.verify(token, secret, { complete: true });✅ Correct
// Explicit algorithm whitelist
jwt.verify(token, secret, { algorithms: ['RS256'] });
jwt.verify(token, secret, { algorithms: ['RS256', 'ES256'] });
jwt.verify(token, secret, { algorithm: 'RS256' });Options
| Option | Type | Default | Description |
|---|---|---|---|
recommendedAlgorithms | string[] | ["RS256","ES256"] | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
trustedSanitizers | string[] | [] | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
trustedAnnotations | string[] | [] | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
strictMode | boolean | false | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Options from Variables, Casts and Spreads
What is read: options are followed within the file — an inline object, a const or a never-reassigned let, a destructure, a member of an object literal, an as / satisfies cast, a spread of any of those, and the single return of a same-file function. NestJS JwtService calls are read at the second argument, where @nestjs/jwt takes them. An injected this.<member> counts as a JWT client only on evidence the class gives (a type annotation resolving to a JWT library, an @Inject(X) of a JWT import, or an assignment from one).
const verifyOptions = { algorithms: ['RS256'] };
jwt.verify(token, key, verifyOptions); // ✅ read through the const
jwt.verify(token, key, { ...verifyOptions, complete: true }); // ✅ spread of a const
jwt.verify(token, key, { ...verifyOptions } as VerifyOptions); // ✅ through the castWhat is not: options from a parameter, an import, a function call, or a spread of one of those cannot be seen. The rule stays silent rather than report an option it cannot prove is missing. NestJS per-call options merge over JwtModule verifyOptions in another file, so they are treated the same way.
// ⚠️ NOT DETECTED - options built elsewhere
jwt.verify(token, key, getVerifyOptions());Mitigation: Keep verify options in a module-level const in the file that verifies.
Wrapper Function Options
Why: Options passed through wrapper functions are not visible.
// ❌ NOT DETECTED - Wrapper hides options
function verifyToken(token: string, opts = {}) {
return jwt.verify(token, secret, opts); // opts may lack algorithms
}
verifyToken(userToken); // Looks safeMitigation: Apply this rule to all modules. Add algorithms in wrappers.
Spread of an Unresolvable Value
Why: { ...getDefaults() } may carry the option where this file cannot see it, so a missing option is not reported. A spread of a same-file const is read (see above).
// ⚠️ NOT DETECTED - the spread source is a call
jwt.verify(token, key, { ...getVerifyOptions() });Dynamic Options Construction
Why: Options built at runtime cannot be analyzed statically.
// ❌ NOT DETECTED - Dynamic options building
const options = getVerifyOptions(); // May or may not have algorithms
jwt.verify(token, secret, options);Mitigation: Use TypeScript interfaces requiring algorithms property.
Library Defaults
Why: Some JWT libraries have secure defaults that aren't visible to the rule.
// ⚠️ FALSE POSITIVE RISK - Some libraries default to safe algorithms
import { verify } from 'secure-jwt-lib';
verify(token, key); // Library may have secure defaultsMitigation: Pass algorithms explicitly even where the library defaults are safe, or disable the rule on that line with a comment saying why.
Further Reading
Use this rule
npm install -D oxlint-plugin-jwt-security{ "jsPlugins": ["oxlint-plugin-jwt-security"], "rules": { "jwt-security/require-algorithm-whitelist": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-jwt-security'], rules: { 'jwt-security/require-algorithm-whitelist': 'error', }, },});oxlint-plugin-jwt-security re-exports eslint-plugin-jwt-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.