RoastMyOpsec

Vulnerabilities

Access-Control-Expose-Headers vs CORS Allow-Origin

January 10, 2022

Access-Control-Expose-Headers vs CORS Allow-Origin is two Fetch CORS knobs. Access-Control-Allow-Origin decides which browser origins may read the response at all. Access-Control-Expose-Headers names extra response headers that script may read besides the CORS-safelisted ones (Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma). Exposing Authorization, Set-Cookie, or a debug request-id to * callers is how you donate metadata. Timing-Allow-Origin is yet another header for Resource Timing, not ACEH. RoastMyOpsec app mode sends a bounded untrusted Origin and reads ACAO and credentials. It does not score Access-Control-Expose-Headers, does not read custom headers as a user, and does not attempt to skip CORS.

The practical difference

ACAO is 'who.' ACEH is 'which headers that who may inspect.' A public weather API can use ACAO * and a short ACEH list. A cookie API cannot use ACAO * at all — see credentials vs wildcard. Max-Age only caches preflight; it does not expose headers.

Safelisted headers are already readable after a successful CORS check. You do not need to expose Content-Type. You do need ACEH for X-Request-Id if the SPA reads it — and you should ask whether that ID belongs in JS.

How to choose what to expose

Expose only headers the first-party SPA must read. Prefer a named ACAO. Do not expose Set-Cookie (browsers will not give JS cookies anyway if HttpOnly). Do not expose WWW-Authenticate details. Do not copy ACEH * from a gateway default onto an authenticated API.

HeaderJobJS can read after CORS?Takeaway
Access-Control-Allow-OriginWhich origin may read the responseN/A — it is the gateAllowlist; see wildcard guide
Access-Control-Expose-HeadersWhich extra headers JS may readOnly names you list (plus safelist)Minimize; no debug dumps
Timing-Allow-OriginResource Timing visibilityDifferent APISee TAO vs CORS
Access-Control-Max-AgePreflight cache TTLNoSee Max-Age vs preflight

What the roast can prove

ACAO reflecting an untrusted Origin can surface. A generous ACEH list is not scored today. Hunt Expose-Headers in your gateway. Pair with verbose API errors if the exposed header is a stack trace id.

Common mistakes

The first mistake is Access-Control-Expose-Headers: * on a user API.

The second mistake is exposing a header that contains a signed URL.

The third mistake is tightening ACAO and leaving ACEH copied from a public CDN preset.

Free audit the URL you own

RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.

Free audit now

FAQ

Does Expose-Headers let other sites call my API?
No. Allow-Origin (and credentials) decide who may read. ACEH only names extra headers after that gate passes.
Should I expose X-Request-Id?
Only if the SPA must show it. Prefer logging it server-side. Do not expose it to every origin.
Does RoastMyOpsec score Access-Control-Expose-Headers?
No. It reads ACAO and credentials from a bounded Origin.

Sources

Related guides