SSL Certificate Analysis Open Port Detection Web Application Scanning DNS Security Audit HTTP Header Analysis Misconfiguration Detection Software Fingerprinting Subdomain Enumeration
SSL Certificate Analysis Open Port Detection Web Application Scanning DNS Security Audit HTTP Header Analysis Misconfiguration Detection Software Fingerprinting Subdomain Enumeration

Cross-Origin Resource Sharing (CORS) controls which external domains are allowed to interact with your web application or API. When configured carelessly, CORS policies become one of the most common and impactful web security misconfigurations — granting untrusted sites access to authenticated data, session tokens, and sensitive endpoints. This guide is for developers, API engineers, and technical managers who want to understand CORS from the ground up and implement it in a way that is both functional and secure.

01

Understand What CORS Does and Why It Matters

Browsers enforce the Same-Origin Policy (SOP) by default, blocking JavaScript on one origin (e.g. https://app.example.com) from reading responses from a different origin (e.g. https://api.example.com). CORS is the mechanism that selectively relaxes this restriction by instructing browsers which cross-origin requests are permitted. If you misconfigure CORS, you effectively punch holes in the SOP for every user of your application.

  • Understand that CORS headers are consumed by browsers — they do not protect server-to-server or curl-based requests.
  • Know the difference between simple requests (GET, POST with basic content types) and preflighted requests (OPTIONS preflight for complex methods or headers).
  • Recognise that a permissive CORS policy combined with Access-Control-Allow-Credentials: true can let an attacker's site read authenticated responses on behalf of your users.
  • Map every endpoint that returns sensitive or authenticated data — these require the strictest CORS rules.
02

Define an Explicit Allowlist of Trusted Origins

The most critical decision in CORS configuration is deciding which origins are trusted. A wildcard (*) tells the browser that any website may read the response — this is acceptable only for fully public, unauthenticated resources such as open CDN assets or public APIs that carry no user-specific data.

  • Never use Access-Control-Allow-Origin: * on endpoints that require authentication or return personalised data.
  • Maintain a server-side allowlist of permitted origins (e.g. https://app.example.com, https://admin.example.com).
  • When responding to a cross-origin request, dynamically check the incoming Origin header against your allowlist and echo back the exact matched origin — do not echo arbitrary origins.
  • Include only production origins in your allowlist; handle development/staging origins via environment-specific configuration, never in production code.
  • Treat subdomains as separate origins — https://evil.example.com is distinct from https://app.example.com; do not use broad subdomain wildcards like *.example.com unless you fully control all subdomains.
03

Restrict Allowed HTTP Methods

The Access-Control-Allow-Methods header tells the browser which HTTP methods are permitted in cross-origin requests. Listing every method increases the attack surface unnecessarily.

  • Only include methods your API genuinely needs — e.g. GET, POST, PUT rather than a blanket GET, POST, PUT, PATCH, DELETE, OPTIONS.
  • Remove TRACE and CONNECT entirely; these are rarely needed and can assist certain attack classes.
  • Confirm your application framework or reverse proxy is not automatically adding permissive method lists without your knowledge.
04

Control Allowed and Exposed Headers

Two separate headers govern which headers cross-origin requests may carry and which response headers JavaScript may read. Both should be as restrictive as possible.

  • Set Access-Control-Allow-Headers to only the headers your API actually reads from incoming requests (e.g. Content-Type, Authorization, X-Requested-With).
  • Do not reflect the incoming Access-Control-Request-Headers value directly back without validation — this can allow clients to inject arbitrary headers.
  • Use Access-Control-Expose-Headers sparingly; only expose non-sensitive custom headers that client-side JavaScript genuinely needs to read (e.g. X-Request-ID).
  • Never expose headers containing tokens, session identifiers, or internal routing information.
05

Handle Credentials with Extra Care

Setting Access-Control-Allow-Credentials: true instructs the browser to include cookies, HTTP authentication, and TLS client certificates in cross-origin requests and to expose the response to JavaScript. This combination dramatically raises the stakes of any CORS misconfiguration.

  • Only set Access-Control-Allow-Credentials: true when your application genuinely requires it (e.g. session-cookie–authenticated APIs consumed by a specific trusted front-end).
  • When credentials are enabled, you must specify an explicit origin — the browser will ignore Access-Control-Allow-Credentials: true if the origin is *, and many frameworks will incorrectly fall back to a wide-open policy.
  • Audit cookie attributes alongside CORS: use SameSite=Strict or SameSite=Lax, Secure, and HttpOnly to add defence-in-depth even if CORS is misconfigured.
  • If an endpoint does not require credentials, ensure Access-Control-Allow-Credentials is omitted or set to false.
06

Set a Sensible Preflight Cache Duration

For preflighted requests, browsers send an OPTIONS request before the actual request. The Access-Control-Max-Age header tells the browser how long (in seconds) it may cache the preflight response, reducing latency. Setting this too high means browsers will cache a stale or permissive policy even after you have tightened your configuration.

  • Set Access-Control-Max-Age to a reasonable value — typically between 300 (5 minutes) and 7200 (2 hours) depending on how frequently your policy changes.
  • Avoid values above 86400 (24 hours); most browsers cap the value anyway, but unnecessarily long caching delays policy updates.
  • During active development or policy changes, reduce Max-Age temporarily to force browsers to re-validate.
07

Validate Origins Server-Side

Browser enforcement of CORS is a client-side control. Malicious actors using curl, scripts, or modified browsers can bypass it entirely and hit your API directly. CORS does not replace authentication, authorisation, or input validation — it complements them.

  • Require proper authentication (e.g. Bearer tokens, signed JWTs, session validation) on all sensitive endpoints regardless of CORS policy.
  • Implement server-side origin validation: parse the Origin header, check it against your allowlist, and reject or log requests from unexpected origins.
  • Do not trust the Referer or Origin headers as the sole access control mechanism — they can be spoofed in non-browser contexts.
  • Apply rate limiting and anomaly detection on your API independently of CORS logic.
  • Ensure your reverse proxy (Nginx, Apache, Caddy, etc.) is not adding its own permissive CORS headers that override your application-level policy.
08

Test and Monitor Your CORS Configuration

CORS misconfigurations are notoriously easy to introduce and hard to spot through code review alone. Systematic testing — both manual and automated — is essential to catch issues before attackers do. Sensagraph continuously scans your web application for CORS misconfigurations, including overly permissive origins, credential exposure, and wildcard policies on authenticated endpoints.

  • Manually test by sending cross-origin requests with crafted Origin headers (e.g. an untrusted domain) and inspecting the response headers to ensure your server does not echo them back.
  • Test with both a matching trusted origin and a non-matching origin to confirm your dynamic allowlist logic works correctly.
  • Test credentialed requests explicitly — send requests with withCredentials: true from an untrusted origin and verify the browser blocks the response.
  • Include CORS header validation in your CI/CD pipeline using integration tests that assert correct header values for each endpoint.
  • Review server and proxy logs for unexpected OPTIONS preflight requests from unknown origins, which may indicate probing.
  • Re-audit your CORS configuration whenever you add new endpoints, change authentication mechanisms, or onboard new front-end consumers.
  • Use automated continuous scanning to detect regressions — CORS policies can silently regress when dependencies or framework defaults change.

Frequently asked questions

Yes, but only if the API returns no user-specific or sensitive data and never uses credentials (cookies or HTTP auth). For any authenticated or personalised endpoint, you must use an explicit origin allowlist instead of a wildcard.

Yes. A permissive CORS policy — especially one that echoes arbitrary origins and allows credentials — can let a malicious website silently read authenticated API responses on behalf of a logged-in user, effectively bypassing Same-Origin Policy protections.

No. CORS is enforced by browsers only. Any HTTP client (curl, Python scripts, server-side code) can call your API directly and ignore CORS headers. Always implement proper authentication and authorisation independently of CORS.

No. The CORS specification does not support wildcard subdomains in the Access-Control-Allow-Origin header — only a literal origin string or * is valid. You must enumerate each permitted subdomain in your server-side allowlist.

Access-Control-Allow-Headers lists the request headers that the browser is permitted to send in a cross-origin request. Access-Control-Expose-Headers lists the response headers that JavaScript on the client side is allowed to read. Both should be kept as restrictive as possible.

Centralise CORS policy enforcement at the API gateway or reverse proxy level where possible to avoid inconsistent configurations across services. Ensure individual microservices do not add their own conflicting CORS headers, and validate that the gateway's policy aligns with your security requirements.