~/tools/dossier-cors

> dossier / cors

send a CORS preflight (OPTIONS) to https://<domain>/ with an `Origin` and `Access-Control-Request-Method`, then surface the `access-control-*` response headers. part of the drwho.me domain dossier.

## overview

CORS (cross-origin resource sharing) is how a server tells a browser which cross-origin pages are allowed to read its responses. before any non-simple request the browser issues a preflight `OPTIONS` carrying `Origin: <caller>` and `Access-Control-Request-Method: <method>` (plus `Access-Control-Request-Headers` if any non-simple headers will be sent), and the server responds with a matching set of `Access-Control-Allow-*` headers: `Allow-Origin` (either `*` or the echoed origin — never a list), `Allow-Methods`, `Allow-Headers`, optional `Allow-Credentials: true` (which is *incompatible* with `Allow-Origin: *` — the browser will reject the response), `Max-Age` (how long the preflight can be cached), and `Expose-Headers` (which response headers become readable to JS beyond the CORS-safelisted set). this tool fires exactly that preflight with `Origin` defaulting to `https://drwho.me` and method defaulting to `GET`, and renders the six AC-* headers side by side. if none come back, the site does not advertise CORS to that origin — which is the common case for sites that are consumed only by their own frontend.

## how to use

  1. enter a bare domainpublic fqdn only. no schemes, ports, or paths.
  2. (optional) set a custom origin and methodCORS responses commonly vary by the requesting `Origin` — a server may echo `Access-Control-Allow-Origin` only for allowlisted origins. similarly `POST` or `PUT` may be denied where `GET` is allowed. supply `origin` and `method` via the MCP tool to probe those branches.
  3. read the AC-* block`—` means the header is absent. `Allow-Origin: *` is a public API signal; an echoed origin (e.g. `https://drwho.me`) means the server has an allowlist and this origin passed. `Allow-Credentials: true` combined with a specific (non-`*`) origin means cookie-carrying cross-site requests are permitted.

## examples

$ example 1 — because origin is `*`, credentials are by spec disallowed. authenticated calls must use `Authorization: Bearer ...`, not cookies.
$ in
api.github.com
# out
GitHub's REST API returns a permissive public CORS set: `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE`, `Access-Control-Expose-Headers: ETag, Link, Location, Retry-After, X-GitHub-*, X-RateLimit-*`. designed for browser-side use without credentials.
$ example 2 — this is the correct default — absence of CORS is a deny, not an allow. a browser cross-origin fetch will fail at the response-read step.
$ in
example.com
# out
a plain static site: all six AC-* rows render `—`. the preflight itself returns 200 or 405, but no CORS headers come back. the muted note reads: `no access-control-* headers returned — site does not advertise CORS to this origin`.

## common mistakes

  • a 2xx on OPTIONS does not imply CORS is configuredmany servers respond 200/204 to an `OPTIONS /` request out of the box (nginx, apache, node frameworks) — sometimes returning the same HTML as `GET`. a 2xx preflight status with zero `access-control-*` headers means exactly the same thing as a 405: CORS is not advertised. always look at the headers, not the status code.
  • `Access-Control-Allow-Origin: *` with `Allow-Credentials: true` is invalidthe CORS spec forbids this combination. browsers will drop the response — not just the `Allow-Credentials` line — and the fetch rejects. servers intending to support cookie-bearing cross-site calls must echo the specific origin back, maintain a per-request allowlist, and include `Vary: Origin` in the response so caches don't cross-pollinate.
  • some CDNs require the preflight method to match a real endpointcloudflare, cloudfront, and fastly sometimes pass OPTIONS through to the origin unmodified — the origin then 404s because it has no route for `OPTIONS /some/api`. the fix at the edge is an `Access-Control-Request-Method`-aware rule that short-circuits with the correct AC-* headers. if your preflight succeeds on `/` but a browser call to `/api/v1/x` fails, the CDN rule probably only covers `/`.

## faq

what's the difference between `*` and an echoed origin?

`Access-Control-Allow-Origin: *` means 'any website may read this response, without credentials'. an echoed origin (the server copies the request's `Origin` header into the response) means 'this specific origin is allowed'. the echoed form is required any time you want to allow cookies/auth (`Access-Control-Allow-Credentials: true`). the echoed form also requires the server to include `Vary: Origin` so intermediary caches keyed by URL don't hand one origin's response to another.

why doesn't every cross-origin request send a preflight?

only non-simple requests do. a request is *simple* (no preflight) when the method is GET/HEAD/POST, the only custom headers are CORS-safelisted (`Accept`, `Accept-Language`, `Content-Language`, `Content-Type` limited to `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`), and there's no ReadableStream upload. anything else — `PUT`, `DELETE`, `Content-Type: application/json`, a custom `X-Foo` header — triggers the preflight.

my call works from curl but fails from the browser — why?

curl doesn't enforce CORS; it's purely a browser security model. the server is responding fine at the HTTP layer, but the browser sees the AC-* headers (or the lack of them) and rejects the response before JS can read it. this tool probes exactly what the browser sees.

what's `Access-Control-Max-Age` for?

it tells the browser how long (in seconds) it can cache this preflight result, keyed by URL + method + headers. `Max-Age: 86400` means 'don't re-preflight for 24 hours'. chrome caps this at 2 hours, firefox at 24 hours. omitting it means every non-simple cross-origin request is preceded by its own preflight round-trip — a real perf cost on chatty APIs.

does this tool send cookies or auth?

no. we send a fixed `User-Agent: drwho-dossier/1.0`, the `Origin` header, and `Access-Control-Request-Method`. no cookies, no `Authorization`. servers that vary CORS by auth state (e.g. a tighter allowlist for anonymous users) will show you the anonymous variant.

## related tools

  • dossier / headers inspect the response headers served at https://<domain>/ — HSTS, CSP, X-Frame-Options, etc.
  • dossier / redirects trace the HTTP(S) redirect chain from https://<domain>/ up to 10 hops.
  • dossier / dns resolve A, AAAA, NS, SOA, CAA, and TXT records for a domain in one go.

## references

  1. Fetch Standard §3.2 — CORS protocol
  2. MDN — Cross-Origin Resource Sharing (CORS)
ad slot · tool-dossier-cors
dossier / cors — drwho.me