no-algorithm-confusion
This rule detects algorithm confusion attacks where symmetric algorithms (HS256, HS384, HS512) are used with asymmetr...
Prevent algorithm confusion attacks using symmetric algorithms with asymmetric keys
🚨 Security rule | 💡 Provides LLM-optimized guidance | ⚠️ Set to error in recommended
Quick Summary
| Aspect | Details |
|---|---|
| CWE Reference | CWE-347 |
| Severity | Critical |
| Auto-Fix | ❌ No auto-fix available |
| Category | Security |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Protecting asymmetric JWT verification from algorithm substitution attacks |
Rule Details
This rule detects algorithm confusion attacks where symmetric algorithms (HS256, HS384, HS512) are used with asymmetric keys (public keys). An attacker can use the public key as an HMAC secret to forge tokens.
Examples
❌ Incorrect
// HS256 with a key that IS public, by how it was made - VULNERABLE
const publicKey = createPublicKey(pem); // node:crypto
jwt.verify(token, publicKey, { algorithms: ['HS256'] });
// Any symmetric algorithm with public key material
const spki = await importSPKI(pem, 'RS256'); // jose
await jwtVerify(token, spki, { algorithms: ['HS384'] });
const signingKey = await jwksClient({ jwksUri }).getSigningKey(kid); // jwks-rsa
jwt.verify(token, signingKey.getPublicKey(), { algorithms: ['HS512'] });
jwt.verify(token, readFileSync('/keys/jwt.pub'), { algorithms: ['HS256'] });
// HMAC and asymmetric algorithms in ONE whitelist (CVE-2015-9235's shape).
// Reported whatever the key is called: the attacker picks HS256 and the
// verifier HMACs with the key material it holds for RS256.
jwt.verify(token, cert, { algorithms: ['RS256', 'HS256'] });✅ Correct
// Asymmetric algorithm with public key - SAFE
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
jwt.verify(token, publicKey, { algorithms: ['ES256'] });
// Symmetric algorithm with shared secret - SAFE
jwt.verify(token, sharedSecret, { algorithms: ['HS256'] });
jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
// A NAME is not evidence: an HMAC secret called PUBLIC_* is still a secret
jwt.verify(token, process.env.PUBLIC_WIDGET_HMAC_SECRET, {
algorithms: ['HS256'],
});A key counts as public only on structural evidence: a PEM PUBLIC KEY /
CERTIFICATE string (inline or one constant away), createPublicKey() /
new X509Certificate() from node:crypto, jose's importSPKI, importX509,
createRemoteJWKSet or createLocalJWKSet, anything a jwks-rsa client
returns, or a file read whose path ends in .pub, .crt or .cer. What the
variable is called is never consulted.
How the Attack Works
- Server uses RS256 (asymmetric) with a public/private key pair
- Attacker obtains the public key (often publicly available)
- Attacker creates a token with
alg: "HS256"signed with the public key as secret - Server accepts HS256 in its algorithm list
- Server verifies using the public key as HMAC secret - attack succeeds
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Key Source Detection Limits
Why: A key is public only on structural evidence. A .pem file can hold a private key as easily as a public one, so its extension is not evidence, and an HMAC-only list against such a key is not reported. A whitelist that mixes HS* with RS*/ES*/PS*/EdDSA is reported whatever the key is.
// ❌ NOT DETECTED - a .pem path says nothing about which half of the pair it holds
const asymKey = fs.readFileSync('public.pem');
jwt.verify(token, asymKey, { algorithms: ['HS256'] });Mitigation: Load public keys with createPublicKey() or jose's importSPKI(), which the rule recognises, or name the file .pub.
Dynamic Algorithm Selection
Why: Algorithm computed at runtime cannot be statically verified against key type.
// ❌ NOT DETECTED - Dynamic algorithm
const algo = getUserPreferredAlgorithm(); // Could return 'HS256'
jwt.verify(token, publicKey, { algorithms: [algo] });Mitigation: Use TypeScript literal types for algorithms. Validate algorithm-key pairs at runtime.
Cross-Module Key Passing
Why: Keys imported from other modules cannot be classified.
// ❌ NOT DETECTED - Key from another module
import { verificationKey } from './keys';
jwt.verify(token, verificationKey, { algorithms: ['HS256'] }); // Is it public or secret?Mitigation: Apply rule across all modules. Use type annotations for key types.
Key From Configuration Object
Why: Properties of configuration objects are not analyzed for key type.
// ❌ NOT DETECTED - Key in config
const config = loadConfig();
jwt.verify(token, config.key, { algorithms: ['HS256'] }); // Key type unknownMitigation: Validate key-algorithm pairs at runtime. Use separate config fields for asymmetric vs symmetric keys.
Further Reading
⚙️ Options
| Option | Type | Default | Description |
|---|---|---|---|
symmetricAlgorithms | string[] | ["HS256","HS384","HS512"] | Algorithms to flag when used with public keys |
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. |
Use this rule
npm install -D oxlint-plugin-jwt-security{ "jsPlugins": ["oxlint-plugin-jwt-security"], "rules": { "jwt-security/no-algorithm-confusion": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-jwt-security'], rules: { 'jwt-security/no-algorithm-confusion': '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.