ZWCorsProxy: Play Any Cross-Origin Video in Your Browser (mp4 & m3u8)
A cross-origin video URL won’t play in your
<video>tag or hls.js because the source server doesn’t send CORS headers. ZWCorsProxy fixes this with trick: prefix the URL with your localhost. The proxy fetches the content server-side, injects permissive CORS headers, and returns it — mp4 passes through transparently (Range/206 intact), m3u8 playlists are auto-rewritten so every segment also flows through the proxy. Single binary, no runtime, bound to 127.0.0.1 so it can never become an open proxy.
Download (pre-built, no install):
Free · Self-contained binary · No runtime required · No sign-up
Why Cross-Origin Videos Fail in the Browser
You embed a video into a web page — say, a product demo or an HLS stream hosted on a CDN — and the player just won’t load it. The console shows a red CORS error: “Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present.”
This happens because browsers enforce the Same-Origin Policy: a script on yoursite.com is not allowed to read responses from cdn.example.com unless that CDN explicitly allows it via the Access-Control-Allow-Origin header. And most video CDNs, anti-hotlink sources, and media servers simply don’t.
You can’t control the remote server’s headers. So the way to make the video playable is to fetch it from somewhere that isn’t bound by CORS — which is exactly what a server-side process can do. ZWCorsProxy is that process, running locally on your machine.
What ZWCorsProxy Does
Start it once. Then, for any cross-origin video, prefix the URL with your local proxy:
http://127.0.0.1:8000/?url=https://cdn.example.com/video.mp4
The proxy does three things:
- Fetches the URL server-side (Go’s HTTP client is not bound by browser CORS).
- Injects permissive CORS headers (
Access-Control-Allow-Origin: *and friends) before returning. - For m3u8/HLS, rewrites every internal URL (segments, keys, variants) to also point at the proxy, so the whole stream flows through it.
Because it runs on the loopback interface (127.0.0.1), there’s no network round-trip penalty — it’s as fast as the upstream connection allows.
Core Features
| Feature | Details |
|---|---|
| mp4 transparent passthrough | Direct binary streams pass through untouched, with Range / 206 / Content-Range fully preserved — scrub the progress bar with zero lag. |
| m3u8 auto-rewrite | The playlist’s segment URLs, #EXT-X-KEY URIs, and master variants are rewritten to base64 paths pointing at the proxy, so every HLS request is CORS-clean. |
| Permissive CORS | Every response gets Access-Control-Allow-Origin: * plus Expose-Headers containing Content-Range, Accept-Ranges, Content-Length — the headers hls.js needs. |
| Range / seek support | Honors Range requests and returns proper 206 Partial Content (the most common video-proxy bug — returning 200 and breaking the progress bar — is avoided). |
| Safe binding | Defaults to 127.0.0.1 (loopback). Use -host to bind another local NIC IP (e.g. a LAN IP for other devices); 0.0.0.0 is rejected to prevent open-proxy abuse. |
| SSRF protection | Refuses to proxy to private ranges (10.x, 172.16-31.x, 192.168.x), loopback, link-local, cloud metadata (169.254.169.254), IPv6 ULA (fc00::/7), and non-HTTP schemes. |
| Optional HTTPS | -https serves over TLS with a built-in 30-year self-signed cert (same cert as ZWServe — trust it for both tools). |
| Anti-hotlink support | -referer lets you fake the Referer header for sources that check it. |
| Zero dependencies | A single self-contained Go binary per platform. No runtime, no node_modules, no install. |
Quick Start on Each Platform
Windows
Double-click zwcorsproxy-win.exe, or from PowerShell / cmd:
.\zwcorsproxy-win.exe
.\zwcorsproxy-win.exe -port 9000
macOS (Apple Silicon)
chmod +x zwcorsproxy-macos
./zwcorsproxy-macos
./zwcorsproxy-macos -port 9000
The macOS build targets Apple Silicon (arm64). On Intel Macs, build from source:
GOOS=darwin GOARCH=amd64 go build zwcorsproxy.go.
Linux
chmod +x zwcorsproxy-linux
./zwcorsproxy-linux
./zwcorsproxy-linux -port 9000
How to Use It
Once running, the proxy prints its address. Prefix any cross-origin video URL and use the result anywhere you’d use the original URL:
http://127.0.0.1:8000/?url=https://example.com/video.mp4
http://127.0.0.1:8000/?url=https://cdn.example.com/master.m3u8
In an HTML <video> tag:
<video src="http://127.0.0.1:8000/?url=https://cdn.example.com/video.mp4" controls></video>
With hls.js:
hls.loadSource('http://127.0.0.1:8000/?url=https://cdn.example.com/master.m3u8');
If the target URL itself contains query parameters (e.g.
?token=abc), URL-encode the whole thing withencodeURIComponentto avoid&being parsed as part of the proxy’s own query string.
mp4 vs m3u8: How Each Is Handled
| Content | What the proxy does | Status code |
|---|---|---|
| mp4 / binary stream | Transparent passthrough via streaming io.Copy. Range/Content-Range/Accept-Ranges and the status code are copied verbatim. |
200 / 206 (as-is) |
| m3u8 playlist | Reads the body, rewrites every internal URL to a proxy base64 path, writes it back. | 200 (body changed) |
The m3u8 rewrite is the crucial part. If the proxy returned the playlist text unchanged, your browser would then try to fetch each .ts segment directly from the original CDN — and hit the same CORS error again. So ZWCorsProxy rewrites every reference (segment lines, #EXT-X-KEY’s URI=, #EXT-X-MEDIA’s URI=, #EXT-X-MAP, master variants) to a base64 path like:
http://127.0.0.1:8000/aHR0cHM6Ly9jZG4vc2VnbWVudC0wLnRz
The browser requests that path; the proxy decodes the base64 back to the original URL and fetches it. The entire HLS chain — master → sub-playlist → segments — flows through the proxy, CORS-clean at every level.
Serving Over HTTPS (Built-in TLS Certificate)
Some browsers and players behave better over HTTPS (mixed-content rules, secure-context APIs). ZWCorsProxy reuses the same built-in 30-year self-signed certificate as ZWServe — bound to 127.0.0.1 + localhost, valid 2026–2056. Trust it and both tools work over HTTPS.
# Export the cert to the current directory
./zwcorsproxy-win.exe -writecert
# → produces zwserver.crt / zwserver.key / zwserver.pem
# Run over HTTPS
./zwcorsproxy-win.exe -https
Install zwserver.crt into your system trust store (Windows: certutil -addstore -f Root zwserver.crt; macOS: security add-trusted-cert …; Linux: copy to the CA directory and run update-ca-certificates). See the ZWServe guide for platform-specific install scripts — the cert is identical.
Pairing ZWCorsProxy with ZWPlayer (and M3U8Grab / ZWServe)
ZWCorsProxy is part of a small family of streaming tools from zwplayer.com. They cover three distinct scenarios:
| Tool | Solves | When to use |
|---|---|---|
| ZWCorsProxy | Cross-origin video won’t play online | You want to stream it live in the browser without downloading |
| M3U8Grab | You want to save a cross-origin m3u8 offline | Download the whole stream (segments, keys, subtitles) for local playback |
| ZWServe | Your local files have CORS issues | Serve a folder with permissive CORS for a web player |
With ZWPlayer — a full-featured web video player — you can drop a ZWCorsProxy URL straight into the player’s source input and play any cross-origin mp4 or m3u8 immediately, with subtitle, chapter, and playlist support intact.
Command-Line Reference
| Flag | Default | Description |
|---|---|---|
-port |
8000 |
Port to listen on. |
-host |
127.0.0.1 |
IP address to bind. Must be a local NIC IP; 0.0.0.0 is rejected. Use a LAN IP (e.g. 192.168.1.50) to let other devices reach the proxy. |
-referer |
(empty) | Fake Referer sent to upstream (for anti-hotlink sites); empty = use the target’s own host. |
-https |
false |
Serve over HTTPS with the built-in self-signed cert. |
-cert |
(empty) | Path to your own TLS cert (PEM); pair with -key. |
-key |
(empty) | Path to your own TLS key (PEM); pair with -cert. |
-writecert |
false |
Write the built-in cert to zwserver.crt/.key/.pem in the current dir and exit. |
-log |
on |
Access log: on (default) or off. |
-h |
— | Show help. |
Common Recipes
zwcorsproxy # 127.0.0.1:8000, HTTP
zwcorsproxy -port 9000
zwcorsproxy -host 192.168.1.50 # bind a LAN IP (other devices can reach it)
zwcorsproxy -referer "https://example.com" # bypass anti-hotlink
zwcorsproxy -https # HTTPS (trust the built-in cert first)
zwcorsproxy -log off # quiet (player seek floods the log)
FAQ
Why does it default to 127.0.0.1? Can I bind a LAN IP?
A CORS proxy is a powerful thing — anyone who can reach it can make it fetch any URL on their behalf. That’s why the default is 127.0.0.1 (loopback only). If you want other devices on your LAN (a phone, a tablet) to use the proxy, run it with -host <your-LAN-IP> (e.g. -host 192.168.1.50). The -host value must be an IP that’s actually assigned to of your network interfaces, and 0.0.0.0 (all interfaces) is deliberately rejected — binding every interface turns your machine into an open proxy that anyone on the network can abuse for scraping or hiding their origin.
I proxied an m3u8, but segments still fail. Why?
Make sure you’re requesting the m3u8 through the proxy (?url=…/master.m3u8), not the raw URL. The proxy detects m3u8 content (by Content-Type or the #EXTM3U prefix) and rewrites segment URLs to base64 paths. If you copy the playlist text out and request segments directly, they’ll bypass the proxy and fail CORS.
Can I use it for non-video requests?
Yes — it’s a general HTTP reverse proxy with CORS headers. It works for images, JSON APIs, fonts, etc. But it’s optimized for video (Range support, m3u8 rewriting, no timeout on long streams). For non-video use, a generic tool may fit better.
Is it safe? What about SSRF?
Yes. Beyond binding to loopback, ZWCorsProxy validates every target: it rejects private IP ranges (RFC1918), loopback, link-local, cloud metadata endpoints (169.254.169.254), IPv6 ULA (fc00::/7), and non-HTTP schemes (file://, ftp://, …). It also strips your browser’s Origin and Cookie headers before forwarding, so your login cookies never leak to the video source.
Does it send my cookies to the video source?
No. Cookies and the Origin header are stripped before the upstream request is made. The proxy identifies itself with a standard browser User-Agent and a Referer (either the you set with -referer, or the target’s own host).
Summary
ZWCorsProxy is the simplest way to make a cross-origin video play in the browser: prefix the URL with your localhost, done. mp4 streams pass through transparently with full Range/seek support; m3u8 playlists are auto-rewritten so the whole HLS chain is CORS-clean. It’s a single self-contained binary, bound to 127.0.0.1 with SSRF protection, and reuses the ZWServe TLS certificate for optional HTTPS.
Try it with ZWPlayer — drop a proxied URL in and play any cross-origin video, with subtitles, chapters, and playlists. For offline use, pair it with M3U8Grab; for serving your own local files, use ZWServe.
— from zwplayer.com