Security

CORS proxy cookie forwarding: what we fixed

A CORS proxy takes whatever headers your request carries and, unless it specifically decides not to, sends them straight on to the target URL you gave it. We'd already excluded Authorization and X-API-Key from that passthrough. Cookie was the one we'd missed — it's fixed now, and the underlying problem is worth understanding if you run a proxy of your own.

· ~6 min read

Why this doesn't come up for most browser calls

If you call corsproxy.dev from fetch() in a browser, you can't actually set a Cookie header on the request yourself — it's one of the forbidden header names in the Fetch spec, alongside Host and Referer. The browser manages it. And since corsproxy.dev doesn't return Access-Control-Allow-Credentials, a browser won't attach its own cookies for api.corsproxy.dev to a cross-origin request in the first place (see our earlier post on CORS with cookies and credentials for the full mechanics of that).

So this gap didn't affect a typical frontend calling the proxy with fetch. It affected anything that isn't a browser: server-side code, a cron job, Postman, a curl command, a mobile app's HTTP client. None of those are bound by the Fetch spec's forbidden-header list — they can set Cookie to whatever they want, and until recently, the proxy forwarded it unchanged.

The actual risk

The proxy's whole job is to fetch a URL you give it and return the response. If your calling code sets a Cookie header — maybe because an HTTP client library attaches a stored cookie jar automatically, maybe because you copy-pasted a request from one target to another — that cookie now travels to whatever url= you pass, not just the domain the cookie was meant for. Point the same client at a different host, by typo or by design, and the cookie goes there too. That's the same shape of problem as forwarding a bearer token: a credential meant for one destination leaking to another because the proxy didn't ask whether it should relay it.

It's a narrower issue than classic SSRF — it requires the caller to be setting a Cookie header at all, not an attacker reaching in from outside — but it's the same category of mistake we already cover in how a CORS proxy can be abused: a proxy that passes a header through by default, without an explicit decision to do so, will eventually send it somewhere nobody intended.

What changed

The proxy already excluded a specific list of inbound headers before forwarding a request upstream: Host, Origin, Referer, X-API-Key, and Authorization. Cookie is now on that list too:

// buildForwardHeaders — headers stripped before the upstream fetch
const headers = new Headers(
  Array.from(request.headers.entries()).filter(
    ([headerName]) => ![
      'host',
      'origin',
      'referer',
      'x-api-key',
      'authorization',
      'cookie',
      'x-corsproxy-via'
    ].includes(headerName.toLowerCase())
  )
);

If you send a Cookie header to api.corsproxy.dev today, it's dropped before the request leaves our network. It never reaches the target, and it isn't logged with the request either.

If you actually need to send a cookie upstream

Sometimes the target genuinely expects a cookie — a legacy API that authenticates with a session cookie instead of a header, for instance. For that, use an explicit reqHeaders override rather than relying on passthrough:

curl "https://api.corsproxy.dev/proxy?url=https%3A%2F%2Fapi.example.com%2Fdata&key=sk_live_...&reqHeaders=cookie:session=abc123"

The distinction matters: passthrough happens whether you meant it to or not, just because the header was present on the inbound request. An override is a value you typed into this specific request, for this specific target. Set-Cookie on the way back is blocked from being set via resHeaders for the same reason in reverse — we don't want a proxied response silently setting cookies in the browser's eyes for our own domain.

The general lesson, if you run your own proxy

Whether you're self-hosting a CORS proxy or writing a small one for internal use, the header-passthrough list deserves a specific, written-down answer, not "whatever the HTTP library forwards by default." At minimum:

None of this replaces target-side protections like blocking private-network addresses — see how a CORS proxy can be abused for that half of the threat model. Header stripping and target filtering are answering different questions: one is "should this credential go anywhere the caller points it," the other is "should this proxy be allowed to reach that address at all."

Reporting a security issue

Found something like this in corsproxy.dev? Email [email protected]. We respond within 24 hours and credit reporters when fixes ship.