Skip to content
Back to skills

Cors Security

ASecurity

Strict CORS: no wildcard with credentials, exact-match origin allowlists, sane preflight cache, minimal exposed headers. Use when generating CORS middleware or framework config, setting CORS headers in API Gateway, CloudFront, or Nginx, or reviewing a cross-origin browser-facing endpoint.

  • 22 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 6, 2026
securityrustgogitapidatabasefrontendsecurity

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned September 6, 2026

npx -y skills add ShieldNet-360/secure-vibe --skill cors-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cors Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cors Security
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shieldnet-360-cors-security-77079b5e/badge)](https://www.skillsdirectory.com/skills/shieldnet-360-cors-security-77079b5e)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
id: cors-security
version: "1.1.0"
title: "CORS Security"
description: "Strict CORS: no wildcard with credentials, exact-match origin allowlists, sane preflight cache, minimal exposed headers. Use when generating CORS middleware or framework config, setting CORS headers in API Gateway, CloudFront, or Nginx, or reviewing a cross-origin browser-facing endpoint."
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: 1100
  compact: 1400
  full: 2200
rules_path: "rules/"
related_skills: ["frontend-security", "api-security"]
last_updated: "2026-08-12"
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
- For any non-public endpoint, allow origins from an explicit allowlist. `*` is
  correct only when the resource is intentionally public *and* cross-origin access
  never carries credentials.
- When a cross-origin request carries credentials — the browser's credentials mode is
  `include`, meaning cookies, TLS client certificates, or browser-managed HTTP auth —
  respond with `Access-Control-Allow-Credentials: true` and a **single explicit
  origin**. On a credentialed request every CORS header loses its wildcard meaning:
  `*` in `Allow-Origin`, `Allow-Methods`, `Allow-Headers`, or `Expose-Headers` is read
  as the literal string `"*"` and the browser blocks the response. That is why
  "just set it to `*` for now" fails in a way that is hard to debug.
- Compare origins by exact match on the **serialized** origin — scheme, host, and port,
  all three. Never substring, prefix, or suffix matching: `endsWith("example.com")`
  accepts `https://example.com.evil.com`, and an unescaped `.` in
  `^https://api.example.com$` accepts `https://apiXexample.com`.
- Include `Vary: Origin` on any response whose headers depend on the request `Origin`,
  so a shared cache cannot hand one origin's `Access-Control-Allow-Origin` to another.
- Restrict `Access-Control-Allow-Methods` to the methods the endpoint accepts and
  `Access-Control-Allow-Headers` to the headers it consumes. An app-set
  `Authorization: Bearer …` header is not "credentials" in the Fetch sense — it is an
  ordinary header that must be listed in `Allow-Headers` and that triggers a preflight.
- Set a bounded `Access-Control-Max-Age` sized to the deployment; an hour to a day is a
  conservative production default. Browsers cap this value regardless of what you send,
  so a very large number buys no extra caching — it only lengthens how long a bad
  allowlist entry stays cached in clients that already fetched it.
- Source allowed origins only from operator-controlled configuration. Do not derive
  them from tenant- or user-controlled data unless an addition passes an authenticated,
  authorized, validated administrative workflow. The risk is not "stored in a
  database", it is "writable by anyone who can create a row".
- When the request `Origin` is not in the allowlist, omit `Access-Control-Allow-Origin`
  entirely. Do not fall back to a default trusted origin and do not echo the rejected
  value — a default-origin fallback silently grants every rejected caller whatever that
  default origin is allowed to do.

### NEVER
- Reflect the `Origin` header without an allowlist check
  (`Access-Control-Allow-Origin: <Origin>` for every caller). With credentials this is
  strictly worse than `*`: the browser refuses `*`, but accepts a reflected origin, so
  the misconfiguration fails open instead of closed.
- Accept the serialized `null` origin on a credentialed or sensitive endpoint. Every
  browser serializes an opaque origin to `null` — sandboxed iframes without
  `allow-same-origin`, `data:` URLs, `file://` documents, some cross-origin redirects.
  Treat any exception as a documented compatibility decision that passed security
  review.
- Allow arbitrary subdomains (`.*\.example\.com$`) without accounting for subdomain
  takeover. Exact matching does not help here: the pattern is implemented correctly and
  a dangling DNS record hands an attacker a matching origin. Pin specific subdomains;
  treat a wildcard as a decision tied to subdomain-ownership controls.
- Expose internal headers via `Access-Control-Expose-Headers`. Limit it to the minimal
  set the frontend genuinely reads.
- Use CORS as authorization. It is a *browser* policy: it does not stop curl,
  server-to-server calls, or any non-browser client. Authenticate the request.

### KNOWN FALSE POSITIVES
- Intentionally public, non-credentialed resources legitimately use
  `Access-Control-Allow-Origin: *` — open-data APIs, and static assets served for
  cross-origin reuse (fonts, images, JS on a CDN). This is the common correct case
  for `*`.
- A few integrations (Stripe.js, Plaid, Auth0) expect specific CORS headers; read the
  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 credentialed requests, it gives untrusted origins the ability
to make cross-origin requests and read the response.

Two failure modes account for most real findings: an origin check that
matches loosely (suffix or unescaped-regex matching), and a mismatch path
that falls back to reflecting or defaulting instead of omitting the header.
Both look correct in review and both fail open.

## References

- `references/verifying-findings.md` — confirm or refute a finding, then lock it
- `rules/cors_safe_config.json`
- [OWASP CORS Origin Header Scrutiny](https://owasp.org/www-community/attacks/CORS_OriginHeaderScrutiny).
- [CWE-942](https://cwe.mitre.org/data/definitions/942.html).
- [Fetch — CORS protocol](https://fetch.spec.whatwg.org/#http-cors-protocol).

Files in this skill

  • SKILL.md6 KB
  • references/verifying-findings.md3.7 KB
  • rules/cors_safe_config.json1.8 KB
  • tests/corpus.json2.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.