CORS proxy
A browser won't let a page read a feed from another site unless that site allows it. In May 2023 I wrote a 42-line Cloudflare Worker to get around that for my own news readers. In 2026 I found I'd published an open relay, and rewrote it to be safe by default.
- Made
- , rewritten
- Built with
- Cloudflare Workers, JavaScript, no dependencies
- Status
- Open source, MIT
The question
Could a few static pages read RSS feeds, news APIs and article pages straight from the browser, with no server of my own? Most feeds don't send CORS headers, so the browser throws the response away even though the request succeeded. A proxy that fetches the URL and adds the missing header fixes that. The question was how small one could be.
How it works
A page asks the Worker for ?url= followed by the address it wants. The Worker fetches it and sends the body back with Access-Control-Allow-Origin set to that page's site. The 2026 version is still one file with no dependencies, and it refuses more than it serves:
- Only the sites you list. Browsers always say which site is asking. Any other site, or a request that doesn't say, gets a 403.
- Reading only.
GETandHEAD, nothing that writes. - Nothing personal passes through. Cookies and
Authorizationheaders stay behind, andSet-Cookienever comes back. Five harmless headers go upstream, with the Worker's own User-Agent. - No private addresses.
localhost,10.x,192.168.x, the169.254.169.254cloud-metadata address and their IPv6 cousins are refused, as are odd ports anduser:password@URLs. - Redirects are checked too. The Worker follows them itself, up to five, and checks each new address like the first, so a redirect can't smuggle in what the first URL couldn't.
- Limits. 5 MB, 10 seconds. The size limit holds even when a site doesn't say how big the response is.
- Optional host list. Name the sites it may fetch from, and it fetches nothing else.
Evidence
- 2023: 42 lines, written in about an hour. The repository has seven commits between 22:13 and 23:13 on 1 May 2023. Four people starred it and three forked it.
- 2026: 163 lines and 16 tests, run with Node's built-in test runner and a stubbed
fetch. They cover allowed and refused sites, the preflight, methods, 19 local, private and malformed targets, the host list, headers in both directions, redirects (including one that points at the metadata address), size limits with and without a length header, timeouts andHEAD. - I also ran it in Cloudflare's local Workers runtime against real sites: a live RSS feed, an
httptohttpsredirect, and each kind of refusal.
What didn't work
- The 2023 version was an open relay. Any site, any method, including
POST,PUTandDELETE, and any URL. Anyone who found a deployment could send any request through it. I didn't look at it again for three years. - It forwarded every header it received, cookies and
Authorizationincluded, to whatever address it was given. - A header in the wrong place. The README promised a browser-like User-Agent. The code set it on the preflight response, where it does nothing, and never on the request it sent.
- The setup steps told you to replace a string that wasn't in the code.
- What the rewrite still can't do. The site check only stops browsers: a script can claim to be any site, so a public deployment should also set the host list. And the private-address check reads the URL, not DNS, because a Worker can't look names up itself.