Skip to main content
interlace
Plugin: jwtRules

require-audience-validation

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

Require audience (aud) claim validation in JWT verify operations

Severity: 🟡 Medium
CWE: CWE-287

Error Message Format

The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:

🔒 CWE-287 OWASP:A07 CVSS:9.8 | Improper Authentication detected | CRITICAL
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A07_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-287 OWASP:A07 CVSS:9.8
Issue DescriptionSpecific vulnerabilityImproper Authentication detected
Severity & ComplianceImpact assessmentCRITICAL
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

Rule Details

This rule mandates audience validation. Without it, tokens intended for other services are accepted.

Examples

❌ Incorrect

jwt.verify(token, secret);
jwt.verify(token, secret, { issuer: 'auth.example.com' });

✅ Correct

jwt.verify(token, secret, { audience: 'https://api.example.com' });
jwt.verify(token, secret, {
  issuer: 'https://auth.example.com',
  audience: 'https://api.example.com',
});

Options

OptionTypeDefaultDescription
knownAudiencesstring[][]Known valid audiences to suggest
trustedSanitizersstring[][]Extra function names to treat as sanitizers
trustedAnnotationsstring[][]Extra JSDoc annotations to treat as safe markers
strictModebooleanfalseDisable false-positive suppression — report even sanitized input

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'], audience: 'api://orders' };
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.

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() });

Runtime Audience Configuration

Why: Audience from runtime config is not visible.

// ❌ NOT DETECTED - Audience from config
jwt.verify(token, secret, { audience: config.audience }); // Might be undefined

Mitigation: Validate config at startup. Use required fields in TypeScript config types.

Wrapper Function

Why: Options passed through wrappers are not traced.

// ❌ NOT DETECTED - Wrapper hides options
function verifyToken(token: string) {
  return jwt.verify(token, secret, { algorithms: ['RS256'] }); // No audience
}

Mitigation: Apply this rule to all modules including utilities.

Further Reading

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.