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
| Aspect | Details |
|---|---|
| CWE Reference | CWE-942 (Permissive Cross-domain Policy) |
| Severity | 🔴 High |
| Auto-Fix | ✅ Yes (suggests origin whitelist) |
| Category | Security |
| Best For | Express.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
| Pattern | Risk | Description |
|---|---|---|
origin: '*' | 🔴 Critical | Allows any domain to access your API |
origin: true | 🔴 High | Reflects request origin, allowing any domain |
cors() (no args) | 🟡 High | Uses permissive defaults |
credentials: true + wildcard | 🔴 Critical | Enables 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
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-942 OWASP:A01 CVSS:7.5 |
| Issue Description | Specific vulnerability | CORS Misconfiguration detected |
| Severity & Compliance | Impact assessment | HIGH |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
Configuration
{
rules: {
"express-security/no-permissive-cors": ["error", {
allowInTests: false,
allowOriginTrue: false,
allowedOrigins: []
}]
}
}Options
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | false | Allow permissive CORS in test files |
allowOriginTrue | boolean | false | Allow origin: true for development |
allowedOrigins | string[] | [] | 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 }));Related Rules
no-cors-credentials-wildcard- Credentials with wildcard originrequire-helmet- Security headers
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.