Skip to main content
interlace
Plugin: lambda-securityRules

no-permissive-cors-response

Detects permissive CORS headers in Lambda API Gateway responses

CWE: CWE-942
OWASP Mobile: M8: Security Misconfiguration

Detects permissive CORS headers in Lambda API Gateway responses. This rule is part of eslint-plugin-lambda-security.

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

Quick Summary

AspectDetails
CWE ReferenceCWE-942 (Permissive Cross-domain Policy)
Severity🔴 High
Auto-Fix✅ Yes
CategorySecurity
Best ForLambda functions returning API Gateway responses

Vulnerability and Risk

Vulnerability: Lambda functions that return responses with Access-Control-Allow-Origin: * allow any website to access the API.

Risk: Combined with credentials, attackers can steal authentication tokens or session data from users who visit malicious sites.

Rule Logic Flow

Examples

❌ Incorrect

// Lambda handler with wildcard CORS - VULNERABLE
export const handler = async (event) => {
  return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ data: 'sensitive' }),
  };
};

// Also vulnerable in error responses
export const handler = async () => {
  return {
    statusCode: 500,
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
    body: JSON.stringify({ error: 'Internal error' }),
  };
};

✅ Correct

// Specific origin - SAFE
export const handler = async (event) => {
  return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': 'https://app.example.com',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ data: 'sensitive' }),
  };
};

// Dynamic origin validation - SAFE
const ALLOWED_ORIGINS = [
  'https://app.example.com',
  'https://admin.example.com',
];

export const handler = async (event) => {
  const origin = event.headers?.origin || event.headers?.Origin;
  const allowedOrigin = ALLOWED_ORIGINS.includes(origin)
    ? origin
    : ALLOWED_ORIGINS[0];

  return {
    statusCode: 200,
    headers: {
      'Access-Control-Allow-Origin': allowedOrigin,
      Vary: 'Origin',
    },
    body: JSON.stringify({ data: 'sensitive' }),
  };
};

Options

OptionTypeDefaultDescription
allowInTestsbooleantrueAllow permissive CORS in test files
{
  "rules": {
    "lambda-security/no-permissive-cors-response": "error"
  }
}

Best Practices

1. Use Environment Variable for Origin

const ALLOWED_ORIGIN = process.env.CORS_ORIGIN || 'https://app.example.com';

return {
  statusCode: 200,
  headers: {
    'Access-Control-Allow-Origin': ALLOWED_ORIGIN,
  },
  body: JSON.stringify(data),
};

2. Add Vary Header for Dynamic Origins

headers: {
  'Access-Control-Allow-Origin': validatedOrigin,
  'Vary': 'Origin'  // Important for caching
}

Known False Negatives

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

Headers from Variable

Why: Headers stored in variables are not analyzed.

// ❌ NOT DETECTED - Headers from variable
const headers = { 'Access-Control-Allow-Origin': '*' };
return { statusCode: 200, headers, body: '...' };

Mitigation: Use inline headers. Validate config at startup.

Headers from Spread

Why: Spread hides actual header values.

// ❌ NOT DETECTED - Headers spread
const baseHeaders = getBaseHeaders(); // May include origin: '*'
return { statusCode: 200, headers: { ...baseHeaders }, body: '...' };

Mitigation: Explicitly define CORS headers inline.

Response factory functions are detected. Both the explicit-return form and the concise arrow form are checked wherever the object literal is written, as long as it carries statusCode or body:

// ✅ DETECTED — explicit return
function createResponse(body) {
  return {
    statusCode: 200,
    headers: { 'Access-Control-Allow-Origin': '*' },
    body: JSON.stringify(body),
  };
}

// ✅ DETECTED — implicit arrow return
const jsonResponse = (statusCode, data) => ({
  statusCode,
  headers: { 'Access-Control-Allow-Origin': '*' },
  body: JSON.stringify(data),
});

A factory is only invisible when the headers themselves come from a variable or a spread — the two cases above.

API Gateway Configuration

Why: CORS configured at API Gateway level is not visible.

// ❌ NOT DETECTED (correctly) - API Gateway handles CORS
// serverless.yml or SAM template has CORS: '*'
return { statusCode: 200, body: '...' };

Mitigation: Review API Gateway CORS configuration separately.

Resources

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

Use this rule

npm install -D oxlint-plugin-lambda-security
.oxlintrc.json
{  "jsPlugins": ["oxlint-plugin-lambda-security"],  "rules": {    "lambda-security/no-permissive-cors-response": "error"  }}
vite.config.ts
import { defineConfig } from 'vite-plus';export default defineConfig({  lint: {    jsPlugins: ['oxlint-plugin-lambda-security'],    rules: {      'lambda-security/no-permissive-cors-response': 'error',    },  },});

oxlint-plugin-lambda-security re-exports eslint-plugin-lambda-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.