Interlace ESLint
ESLint Interlace

require-expiration

**Severity:** � Medium

Require expiration claim (exp) or expiresIn option in JWT signing

Severity: 🟡 Medium
CWE: CWE-613

Error Message Format

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

⚠️ CWE-613 OWASP:A07 CVSS:5.4 | Insufficient Session Expiration detected | MEDIUM
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A07_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-613 OWASP:A07 CVSS:5.4
Issue DescriptionSpecific vulnerabilityInsufficient Session Expiration detected
Severity & ComplianceImpact assessmentMEDIUM
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

Rule Details

This rule mandates expiration in JWT tokens. Tokens without expiration are valid forever, increasing the exposure window if compromised.

Examples

❌ Incorrect

jwt.sign(payload, secret);
jwt.sign(payload, secret, {});
jwt.sign(payload, secret, { algorithm: 'RS256' });

✅ Correct

// expiresIn option
jwt.sign(payload, secret, { expiresIn: '1h' });

// exp in payload
jwt.sign({ sub: 'user', exp: Math.floor(Date.now() / 1000) + 3600 }, secret);

Options

{
  "jwt/require-expiration": ["error", {
    "maxExpirationSeconds": 86400
  }]
}

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Dynamic Payload Construction

Why: The rule checks literal payload objects; computed payloads are not analyzed.

// ❌ NOT DETECTED - Payload built dynamically
function buildPayload(userId: string) {
  return { sub: userId }; // No exp claim
}
jwt.sign(buildPayload('user123'), secret); // Missing expiration

Mitigation: Add expiration in the sign() options as a backup.

Spread Operator Payload

Why: Spread objects hide the actual claims at lint time.

// ❌ NOT DETECTED - exp might be missing in baseClaims
const baseClaims = getBaseClaims();
jwt.sign({ ...baseClaims, sub: userId }, secret);

Mitigation: Always include expiresIn in options, not just exp in payload.

Variable Payload Reference

Why: Variable contents are not tracked across assignments.

// ❌ NOT DETECTED - Payload from variable
const payload = { sub: 'user' }; // No exp
jwt.sign(payload, secret); // Variable reference not analyzed

Mitigation: Use inline objects with TypeScript interfaces that require exp.

Cross-File Payload Creation

Why: Payloads created in other modules are not visible.

// ❌ NOT DETECTED - Payload from imported function
import { createTokenPayload } from './tokens';
jwt.sign(createTokenPayload(user), secret); // Depends on implementation

Mitigation: Apply rule to all modules. Use TypeScript interfaces with required exp field.

Excessive Expiration Values

Why: Very large expiresIn values (e.g., '100y') pass the check but are effectively non-expiring.

// ❌ NOT DETECTED (by default) - Effectively non-expiring
jwt.sign(payload, secret, { expiresIn: '100y' });

Mitigation: Use maxExpirationSeconds option to enforce reasonable limits.

Further Reading

On this page