web: download U-Boot through OpenIPC's own cors-proxy worker - #118
Merged
Conversation
The public-proxy fallback shipped in #115 does not survive contact with reality: cors.eu.org — the only one of eight that served a 250 KB asset at all — rate-limits after roughly eight requests and then returns 429 to every origin, with no ACAO on the error, which the browser reports as the same "No Access-Control-Allow-Origin" failure as the original bug. OpenIPC already operates a Cloudflare Worker for exactly this problem (github.com/OpenIPC/cors-proxy, shared with rnd-player). It fetches upstream without Origin/Referer, so GitHub's asset host answers it as a plain server-to-server request, and it follows the 302 to release-assets.githubusercontent.com transparently. Verified against the live worker: it returns the asset byte-for-byte, digest matching the one published by api.github.com. Two things made this drop in cleanly. defib is served from openipc.github.io/defib/, the same origin as rnd-player, so it already satisfies the worker's origin allowlist with no worker-side change. And the worker exempts localhost from HMAC, so local development needs no key at all. Deployed builds do need one. defib has no bundler — Pages uploads web/ verbatim — so the key is written into web/config.js by the Pages workflow from the CORS_PROXY_HMAC_KEY repository secret, mirroring rnd-player's Vite define and keeping the key out of the repo. The committed config.js carries an empty key for local use. The signature covers `floor(t/300):url` and is cross-checked in tests against the worker's own auth.ts: fresh and previous-window signatures verify, while stale timestamps, tampered target URLs and wrong keys are rejected. Order is direct, then the OpenIPC worker, then the public proxies. Direct stays first so the tool sheds its proxy dependency the moment GitHub restores CORS; the public proxies stay last as a threadbare safety net. Every candidate is still SHA-256 verified before anything is flashed, so the worker is trusted for availability but not for content. Verified end-to-end in headless Chromium against the live worker: the direct fetch is blocked by CORS, the worker then returns 256459 bytes whose SHA-256 matches, and the UI reports "Downloaded u-boot-gk7205v300-universal.bin via OpenIPC cors-proxy — SHA-256 verified". NOTE: this only fixes the deployed site once CORS_PROXY_HMAC_KEY is added to the repository secrets. Until then the deployed origin sends unsigned requests, the worker answers 400, and the download falls through to the rate-limited public proxies as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #113 — properly this time.
Important
This only fixes the deployed site once
CORS_PROXY_HMAC_KEYis added to this repo's Actions secrets (same value as the worker'sHMAC_SECRET). Until then the deployed origin sends unsigned requests, the worker replies400, and downloads fall through to the rate-limited public proxies exactly as now. Everything else is in place and tested.Why #115 wasn't enough
cors.eu.org— the only one of eight public proxies that could serve a 250 KB asset — rate-limits after ~8 requests, then returns 429 to every origin with no ACAO on the error. The browser surfaces that as "No 'Access-Control-Allow-Origin' header is present": indistinguishable from the original bug.Using OpenIPC's own worker
OpenIPC/cors-proxy already solves this for rnd-player. It fetches upstream without
Origin/Referer, so GitHub's asset host answers as a plain server-to-server request, and follows the 302 torelease-assets.githubusercontent.comtransparently.Verified against the live worker:
/health{"status":"ok"}0160bbcb…— byte-exactOrigin: https://evil.example403 origin_not_allowedopenipc.github.io, unsigned400 missing_paramsTwo things made it drop in cleanly:
openipc.github.io/defib/— the same origin asopenipc.github.io/rnd-player/— so it already satisfiesALLOWED_ORIGINS.localhostfrom HMAC.Key handling
defib has no bundler (Pages uploads
web/verbatim), sopages.ymlwritesweb/config.jsfrom theCORS_PROXY_HMAC_KEYsecret at deploy time — mirroring rnd-player's Vitedefineand keeping the key out of the repo. The committedconfig.jshas an empty key for local use.I deliberately did not commit the key: the worker uses one shared
HMAC_SECRET, and since both projects share an origin, publishing it here would strip rnd-player's protection too.The signature covers
floor(t/300):url. Rather than trust my reading of the spec, the tests import the worker's actualsrc/auth.tsand verify against it:Source order
Every candidate is still SHA-256 verified against the
api.github.comdigest, so the worker is trusted for availability but not for content.Verification
End-to-end in headless Chromium against the live worker (localhost origin, HMAC-exempt path):
The direct attempt is blocked by CORS first, then the worker succeeds — the exact failure path from the issue, now recovering.
pages.ymlvalidated as YAML; injection step simulated with the secret both set and unsetFollow-up to consider
The worker's rate limit is shared with rnd-player's video traffic. If firmware downloads ever grow enough to matter, a separate worker instance with its own secret would isolate them.
🤖 Generated with Claude Code