<< All versions

Skill v1.0.0

currentAutomated scan100/100
nguyencongnamit/skills-library/cors-security
──Details
PublishedSeptember 27, 2026 at 09:28 AM
Content Hashsha256:b104455327d8ddfc...
Git SHA617c977343ba
──Files
Files (1 file, 5.6 KB)
SKILL.md5.6 KBactive
SKILL.md · 119 lines · 5.6 KB

id: cors-security version: "1.0.0" title: "CORS Security" description: "Strict CORS configuration: no wildcard with credentials, allowlist-based origins, sensible preflight cache, minimal exposed headers" category: prevention severity: high applies_to:

  • "when generating CORS middleware or framework config"
  • "when wiring API Gateway / Cloud Front / Nginx CORS headers"
  • "when reviewing a cross-origin browser-facing endpoint"

languages: ["*"] token_budget: minimal: 1000 compact: 1000 full: 2000 rules_path: "rules/" related_skills: ["frontend-security", "api-security"] last_updated: "2026-06-20" sources:

  • "OWASP HTML5 Security Cheat Sheet — CORS"
  • "CWE-942 — Permissive Cross-domain Policy with Untrusted Domains"
  • "Fetch Living Standard (CORS)"

CORS Security

Rules (for AI agents)

ALWAYS

  • Use an allowlist of origins, not *. Reflect the incoming Origin

header only when it matches a known entry from configuration (or matches a precompiled regex of operator-controlled hostnames).

  • If responses include credentials (cookies, Authorization), set

Access-Control-Allow-Credentials: true and ensure Access-Control-Allow-Origin is a single specific origin string — never *.

  • Include Vary: Origin on responses whose body depends on the request

Origin, so caches don't serve one origin's response to another.

  • Restrict preflight Access-Control-Allow-Methods to the actual methods

the endpoint accepts; restrict Access-Control-Allow-Headers to the actual headers consumed.

  • Set Access-Control-Max-Age to a sensible value (≤ 86400 in production)

to amortize preflight latency without locking in a bad allowlist.

  • Maintain the allowlist in code (or in a config file checked into

source), not derived from a database — so attackers can't add their origin by inserting a row.

NEVER

  • Set Access-Control-Allow-Origin: * together with

Access-Control-Allow-Credentials: true. The Fetch spec forbids it for a reason — browsers will refuse the response, but the bigger problem is that an upstream proxy / cache may already have leaked it.

  • Reflect the Origin header without an allowlist check (`Access-Control-

Allow-Origin: <Origin> for every incoming origin). That's the same as *` for credentials but with worse caching behavior.

  • Allow null as an Origin. null is what Chrome sends from sandboxed

iframes, data: URIs, and file:// — none of which should have credentialed access to your API.

  • Allow arbitrary subdomains with a regex like .*\.example\.com$ without

considering subdomain takeover. Pin specific subdomains; treat *.example.com as a deliberate decision tied to subdomain ownership controls.

  • Expose internal headers via Access-Control-Expose-Headers. Limit to

the minimal set the frontend genuinely needs.

  • Use CORS as authorization. CORS is a browser policy; it does not stop

server-to-server, curl, or non-browser clients. Authenticate the request properly.

KNOWN FALSE POSITIVES

  • Truly public, unauthenticated APIs (e.g., open data, marketing CDN

endpoints) can legitimately use Access-Control-Allow-Origin: * without credentials.

  • Internal admin tools restricted to a private network can use a single

fixed origin; the wildcard concern doesn't apply because there are no cross-origin callers.

  • A handful of integrations (Stripe.js, Plaid, Auth0) expect specific CORS

headers — read each provider's CORS section before relaxing the baseline.

Context (for humans)

CORS is widely misunderstood as a security control. It isn't — it's a relaxation of the same-origin policy. The security control is authentication. CORS misconfiguration matters because, when combined with cookies or Authorization headers, it gives untrusted origins the ability to make credentialed cross-origin requests and read the response.

This skill is short by design — the matrix of bad combinations is finite and the rules are blunt.

Verify & lock (triaging a finding)

A scanner/review hit is a candidate, not a confirmed bug. Confirm it, fix it, then lock it so it can't come back.

  1. Confirm it's real (probe the suspect input). Replay a request to the endpoint

with a forged Origin header the allowlist should reject — Origin: https://evil.example, then separately Origin: null — and send credentials (cookie or Authorization). A real hit: the response echoes Access-Control-Allow-Origin: https://evil.example (or null, or *) together with Access-Control-Allow-Credentials: true, meaning the attacker's page can read the credentialed response. A false positive: the server omits the ACAO header, returns a fixed trusted origin regardless of input, or sends * with no credentials on a genuinely public endpoint — also watch for *.example.com regexes that match attacker-controlled subdomains, and missing Vary: Origin on cached responses.

  1. Fix, then lock with a regression test (unit or integration — dev's call): assert that

requests with Origin: https://evil.example and Origin: null yield no reflected ACAO (never * paired with Allow-Credentials: true), while a known-good origin from the allowlist is reflected as a single specific value with Vary: Origin present. Commit it to CI so the guard can't be silently dropped in a later refactor.

References

All versions