Skip to content

fix: stop echoing all request headers in CORS response headers#241

Open
Alice39s wants to merge 1 commit intotiagozip:mainfrom
Alice39s:fix/cors-header-leak
Open

fix: stop echoing all request headers in CORS response headers#241
Alice39s wants to merge 1 commit intotiagozip:mainfrom
Alice39s:fix/cors-header-leak

Conversation

@Alice39s
Copy link
Copy Markdown

CORS Header Leakage Fix for @elysiajs/cors

Summary

The @elysiajs/cors plugin ships with allowedHeaders and exposeHeaders defaulting to true, which causes it to echo every incoming request header back in the Access-Control-Allow-Headers and Access-Control-Expose-Headers response headers – even on non‑preflight responses.
This results in internal infrastructure headers (Cloudflare cf-*, x-forwarded-*, cdn-loop, etc.) appearing in CORS response headers, where they are unnecessary and may leak information about the backend environment.


Root Cause

  • @elysiajs/cors v1.4.1 configures allowedHeaders: true and exposeHeaders: true as defaults.
    According to the plugin documentation, when either property is set to true the plugin calls processHeaders(request.headers) – it extracts every header key from the incoming request and joins them into a comma‑separated string for the corresponding CORS response header.
  • This behaviour inadvertently reflects all request headers, including those added by reverse proxies and CDNs (e.g., cf-connecting-ip, x-forwarded-for, cdn-loop), into Access-Control-Allow-Headers and Access-Control-Expose-Headers.

Fix

Explicitly set allowedHeaders and exposeHeaders in all three CORS configurations so that only headers required by the client are advertised.

File Endpoint allowedHeaders exposeHeaders
cap.js POST /:siteKey/challenge ["content-type"] []
siteverify.js POST /:siteKey/siteverify ["content-type"] []
assets.js GET /assets/* (not set) []

Why ["content-type"]?

  • Content-Type: application/json is the only non‑CORS‑safelisted request header that the browser sends on these POST endpoints.
  • All other headers (accept, accept-language, etc.) are CORS‑safelisted request headers and do not require explicit listing in Access-Control-Allow-Headers – see the CORS protocol specification.
    Notably, a Content-Type header with a value of application/json is not safelisted, therefore it must be explicitly allowed for the preflight to succeed.

Why [] for exposeHeaders?

  • No frontend code currently reads custom response headers via response.headers.get().
  • The Access-Control-Expose-Headers header is only needed when a client must access non‑safelisted response headers.
  • Setting exposeHeaders: [] effectively removes the Access-Control-Expose-Headers header from the response, preventing unnecessary exposure of internal headers. (A true value would mirror every request header into this header as well.)

Before / After

Before – The response reflected a long list of all request headers, including internal infrastructure headers:

access-control-allow-headers:
  host, user-agent, content-length, accept,
  accept-encoding, accept-language, cache-control, connection, dnt, origin,
  pragma, referer, sec-fetch-dest, sec-fetch-mode, cdn-loop, cf-connecting-ip,
  cf-ipcity, cf-ipcontinent, cf-ipcountry, cf-iplatitude, cf-iplongitude,
  cf-postal-code, cf-ray, cf-timezone, cf-visitor, cf-warp-tag-id, priority,
  sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, sec-fetch-site,
  x-forwarded-for, x-forwarded-proto

access-control-expose-headers:
  host, user-agent, content-length, accept,
  accept-encoding, accept-language, cache-control, connection, dnt, origin,
  pragma, referer, sec-fetch-dest, sec-fetch-mode, cdn-loop, cf-connecting-ip,
  cf-ipcity, cf-ipcontinent, cf-ipcountry, cf-iplatitude, cf-iplongitude,
  cf-postal-code, cf-ray, cf-timezone, cf-visitor, cf-warp-tag-id, priority,
  sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, sec-fetch-site,
  x-forwarded-for, x-forwarded-proto

After – Only the necessary CORS header is present, and no Access-Control-Expose-Headers is emitted:

access-control-allow-headers: content-type

(Access-Control-Expose-Headers is no longer present unless explicitly required by a future frontend feature.)


References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant