# CORS headers configured with specific allowed origins

- **Pattern:** `ab-000383` (`api-security.input-validation.cors-configured`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000383
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000383)

## Why it matters

A CORS wildcard (`Access-Control-Allow-Origin: *`) combined with `Access-Control-Allow-Credentials: true` is a configuration that browsers reject — but the attempt signals a misunderstanding of CORS that often accompanies other auth mistakes. The real risk is a permissive CORS policy that allows arbitrary origins with credentials: CWE-942 (Permissive Cross-domain Policy) and CWE-352 (CSRF) describe how this enables cross-site request forgery against cookie-authenticated APIs. OWASP API8 (Security Misconfiguration) covers CORS as a first-class misconfiguration class. Attackers host malicious pages that call your API with the victim's session cookie — the browser delivers it because your CORS policy permits the attacker's origin.

## Severity rationale

Medium because exploiting CORS misconfigurations requires a victim to visit an attacker-controlled page, reducing reach but enabling session-riding attacks against authenticated users.

## Remediation

Explicitly enumerate allowed origins in `src/middleware.ts` or your framework's CORS config. Never reflect the request `Origin` header back without validating against a known-good list.

```ts
// next.config.js (Next.js headers)
module.exports = {
  async headers() {
    return [{
      source: '/api/:path*',
      headers: [
        { key: 'Access-Control-Allow-Origin', value: 'https://app.example.com' },
        { key: 'Access-Control-Allow-Credentials', value: 'true' },
        { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE,OPTIONS' }
      ]
    }]
  }
}

// For dynamic multi-tenant origins, validate against an allowlist:
const ALLOWED = new Set(['https://app.example.com', 'https://staging.example.com'])
const origin = req.headers.get('origin') ?? ''
if (ALLOWED.has(origin)) res.headers.set('Access-Control-Allow-Origin', origin)
```

## Detection

- **ID:** `cors-configured`
- **Severity:** `medium`
- **What to look for:** Enumerate every relevant item. Check the CORS middleware or headers configuration. Verify that `Access-Control-Allow-Origin` is set to specific, trusted domains rather than `*` (wildcard). If `Access-Control-Allow-Origin: *` is used with `Access-Control-Allow-Credentials: true`, it is insecure.
- **Pass criteria:** At least 1 of the following conditions is met. CORS headers specify allowed origins explicitly. If a wildcard is used, credentials are not allowed in requests. Alternatively, origins are dynamically validated against a whitelist.
- **Fail criteria:** `Access-Control-Allow-Origin: *` is combined with `Access-Control-Allow-Credentials: true`, or the allowed origin is the wildcard without credentials check.
- **Skip (N/A) when:** The API is not accessed from browsers (e.g., server-to-server API only).
- **Detail on fail:** `"CORS headers allow all origins with wildcard and credentials: Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true"` or `"CORS not configured — requests from different origins will be blocked"`
- **Remediation:** Configure CORS to allow specific origins:

  ```ts
  import cors from 'cors'

  app.use(cors({
    origin: ['https://example.com', 'https://app.example.com'],
    credentials: true
  }))
  ```

  Or for dynamic origin validation:

  ```ts
  const allowedOrigins = ['https://example.com', 'https://app.example.com']

  app.use(cors({
    origin: (origin, callback) => {
      if (allowedOrigins.includes(origin)) {
        callback(null, true)
      } else {
        callback(new Error('CORS not allowed'))
      }
    }
  }))
  ```

## External references

- cwe CWE-942
- cwe CWE-352
- owasp A05
- owasp API8

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-000383
