Interlace ESLint
ESLint Interlace
ExpressRules

no-permissive-cors

Detects overly permissive CORS configurations in Express.js applications. This rule is part of [`eslint-plugin-express-security`](https://www.npmjs.com/package/

Keywords: CORS, cross-origin resource sharing, CWE-942, security, ESLint rule, origin validation, wildcard CORS, Access-Control-Allow-Origin, auto-fix, LLM-optimized

Detects overly permissive CORS configurations in Express.js applications. This rule is part of eslint-plugin-express-security and provides LLM-optimized error messages.

⚠️ This rule errors by default in the recommended config.

Quick Summary

AspectDetails
CWE ReferenceCWE-942 (Permissive Cross-domain Policy)
Severity🔴 High
Auto-Fix✅ Yes (suggests origin whitelist)
CategorySecurity
Best ForExpress.js APIs, REST services, web applications

Vulnerability and Risk

Vulnerability: Misconfigured CORS policies using wildcard * origin or reflecting the Origin header without validation allow any website to access your API.

Risk: Attackers can create malicious websites that force users' browsers to make requests to your vulnerable API. Since browsers include cookies, attackers can steal sensitive data or perform unauthorized actions.

How CORS Attacks Work

Rule Logic Flow

Detection Patterns

PatternRiskDescription
origin: '*'🔴 CriticalAllows any domain to access your API
origin: true🔴 HighReflects request origin, allowing any domain
cors() (no args)🟡 HighUses permissive defaults
credentials: true + wildcard🔴 CriticalEnables credential theft

Examples

❌ Incorrect

import cors from 'cors';

// Wildcard origin - VULNERABLE
app.use(cors({ origin: '*' }));

// Reflect any origin - VULNERABLE
app.use(cors({ origin: true }));

// No options = permissive defaults - VULNERABLE
app.use(cors());

// Credentials with origin reflection - CRITICAL
app.use(
  cors({
    origin: true,
    credentials: true,
  }),
);

✅ Correct

import cors from 'cors';

// Explicit whitelist - SAFE
app.use(
  cors({
    origin: ['https://app.example.com', 'https://admin.example.com'],
    credentials: true,
  }),
);

// Dynamic validation - SAFE
const allowedOrigins = ['https://app.example.com'];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error('Not allowed by CORS'));
      }
    },
    credentials: true,
  }),
);

// Environment-based configuration - SAFE
app.use(
  cors({
    origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
    credentials: true,
  }),
);

Error Message Format

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

🔒 CWE-942 OWASP:A01 CVSS:7.5 | CORS Misconfiguration detected | HIGH
   Fix: Review and apply the recommended fix | https://owasp.org/Top10/A01_2021/

Message Components

ComponentPurposeExample
Risk StandardsSecurity benchmarksCWE-942 OWASP:A01 CVSS:7.5
Issue DescriptionSpecific vulnerabilityCORS Misconfiguration detected
Severity & ComplianceImpact assessmentHIGH
Fix InstructionActionable remediationFollow the remediation steps below
Technical TruthOfficial referenceOWASP Top 10

Configuration

{
  rules: {
    "express-security/no-permissive-cors": ["error", {
      allowInTests: false,
      allowOriginTrue: false,
      allowedOrigins: []
    }]
  }
}

Options

OptionTypeDefaultDescription
allowInTestsbooleanfalseAllow permissive CORS in test files
allowOriginTruebooleanfalseAllow origin: true for development
allowedOriginsstring[][]Allowed origins that should not trigger warnings

Best Practices

1. Use Environment Variables

const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || [];

app.use(
  cors({
    origin: allowedOrigins,
    credentials: true,
  }),
);

2. Validate Origin Dynamically

app.use(
  cors({
    origin: (origin, callback) => {
      // Allow requests with no origin (same-origin, Postman, etc.)
      if (!origin) return callback(null, true);

      if (allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error('Not allowed by CORS'));
      }
    },
  }),
);

3. Different Origins for Dev/Prod

const allowedOrigins =
  process.env.NODE_ENV === 'production'
    ? ['https://app.example.com']
    : ['http://localhost:3000', 'http://localhost:3001'];

app.use(cors({ origin: allowedOrigins }));

Known False Negatives

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

Options from Variable

Why: CORS options stored in variables are not analyzed.

// ❌ NOT DETECTED - Options from variable
const corsOptions = { origin: '*' };
app.use(cors(corsOptions));

Mitigation: Use inline CORS options. Validate config at startup.

Dynamic Origin Validation Flaws

Why: The logic inside origin validation functions is not analyzed.

// ❌ NOT DETECTED - Flawed validation
app.use(
  cors({
    origin: (origin, cb) => {
      // Bug: Substring match allows evil.example.com
      if (origin?.includes('example.com')) {
        cb(null, true);
      }
    },
  }),
);

Mitigation: Use exact match with allowlist. Implement thorough URL validation.

Spread Configuration

Why: Spread objects hide their configuration.

// ❌ NOT DETECTED - Origin in spread
const base = { origin: '*' };
app.use(cors({ ...base }));

Mitigation: Avoid spreading CORS options. Define inline.

Framework CORS Wrappers

Why: Framework-specific CORS middleware is not recognized.

// ❌ NOT DETECTED - Custom CORS middleware
import { setCors } from '@my-framework/middleware';
app.use(setCors({ allowAll: true })); // Permissive!

Mitigation: Apply rule patterns to framework wrappers. Review framework docs.

Late Configuration Change

Why: Configuration modified after initial setup is not tracked.

// ❌ NOT DETECTED - Options modified later
const opts = { origin: ['https://safe.com'] };
if (process.env.DEV) opts.origin = '*'; // Dangerous override!
app.use(cors(opts));

Mitigation: Use immutable configuration. Validate at startup.

Resources

On this page