Skip to main content
interlace
Plugin: jwtRules

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

OptionTypeDefaultDescription
recommendedAlgorithmsstring[]["RS256","ES256"]No effect — accepted so existing configs stay valid; the rule does not read it yet.
trustedSanitizersstring[][]No effect — accepted so existing configs stay valid; the rule does not read it yet.
trustedAnnotationsstring[][]No effect — accepted so existing configs stay valid; the rule does not read it yet.
strictModebooleanfalseNo 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 cast

What 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 safe

Mitigation: 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 defaults

Mitigation: 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
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-jwt-security"],  "rules": {    "jwt-security/require-algorithm-whitelist": "error"  }}
vite.config.ts
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.