no-weak-secret
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
Require strong secrets (256+ bits) for HMAC-based JWT signing
Severity: 🟠 High
CWE: CWE-326
Error Message Format
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
⚠️ CWE-326 OWASP:A04 CVSS:5.9 | Inadequate Encryption Strength detected | MEDIUM
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A04_2021/Message Components
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-326 OWASP:A04 CVSS:5.9 |
| Issue Description | Specific vulnerability | Inadequate Encryption Strength detected |
| Severity & Compliance | Impact assessment | MEDIUM |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
Rule Details
This rule detects weak secrets used for HMAC-based JWT signing. Weak secrets can be brute-forced offline, allowing attackers to forge tokens.
Examples
❌ Incorrect
// Known weak patterns
jwt.sign(payload, 'secret');
jwt.sign(payload, 'password');
jwt.sign(payload, 'changeme');
// Short secrets (<32 characters)
jwt.sign(payload, 'shortkey');
// The same literals one const away, behind an env fallback, or in bytes
jwt.sign(payload, process.env.JWT_SECRET || 'secret');
await new SignJWT(claims)
.setProtectedHeader({ alg: 'HS256' })
.sign(new TextEncoder().encode('secret'));
expressjwt({ secret: 'shhhhhhared-secret', algorithms: ['HS256'] });✅ Correct
// Environment variable
jwt.sign(payload, process.env.JWT_SECRET);
// Long secret (32+ characters)
jwt.sign(payload, 'ThisIsAVeryStrongSecretThatIs32Ch+');
// Generated secret
jwt.sign(payload, crypto.randomBytes(32).toString('hex'));
// Test files are skipped; node:crypto Sign objects are not JWT signers
signer.sign(privateKey, 'base64'); // 'base64' is an encoding, not a secretOptions
| Option | Type | Default | Description |
|---|---|---|---|
minSecretLength | integer | 32 | Minimum secret length in characters |
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. |
{
"jwt/no-weak-secret": ["error", {
"minSecretLength": 32
}]
}| Option | Type | Default | Description |
|---|---|---|---|
minSecretLength | integer | 32 | Minimum secret length (chars) |
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Reassignable Bindings
Why: the secret is read through a same-file const, an || / ?? fallback and a byte wrapper, but a let / var can be reassigned.
const weakSecret = 'abc';
jwt.sign(payload, weakSecret); // ❌ detected through the const
// ⚠️ NOT DETECTED - a let can be reassigned
let secret = 'abc';
jwt.sign(payload, secret);Mitigation: Use environment variables. Apply TypeScript branded types for validated secrets.
Concatenated Secrets
Why: String operations are not evaluated statically.
// ❌ NOT DETECTED - Concatenated weak parts
const part1 = 'short';
const part2 = 'key';
jwt.sign(payload, part1 + part2); // Still weak, but not detectedMitigation: Use single, long secret strings. Avoid dynamic secret construction.
Encoded Weak Secrets
Why: Base64/hex encoding can hide a weak underlying secret.
// ❌ NOT DETECTED - Encoded but short underlying value
const encoded = Buffer.from('weak').toString('base64'); // 'd2Vhaw=='
jwt.sign(payload, encoded); // String is long enough but entropy is lowMitigation: Validate secret entropy at runtime. Use cryptographically random secrets.
Environment Variable Values
Why: The rule trusts environment variables but cannot verify their actual values.
// ❌ NOT DETECTED - Weak secret in env var
// .env: JWT_SECRET=password
jwt.sign(payload, process.env.JWT_SECRET); // Trusts the env varMitigation: Use secret rotation policies. Validate secret strength at application startup.
Secrets from Configuration Files
Why: Values loaded from config files are treated as safe.
// ❌ NOT DETECTED - Weak secret in config
import { JWT_SECRET } from './config'; // Config contains 'weak123'
jwt.sign(payload, JWT_SECRET);Mitigation: Apply secret validation at config load time. Use environment variables for secrets.
Further Reading
Use this rule
npm install -D oxlint-plugin-jwt-security{ "jsPlugins": ["oxlint-plugin-jwt-security"], "rules": { "jwt-security/no-weak-secret": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-jwt-security'], rules: { 'jwt-security/no-weak-secret': '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.