Skip to content

web: download U-Boot through OpenIPC's own cors-proxy worker - #118

Merged
widgetii merged 1 commit into
masterfrom
feature/openipc-cors-proxy
Jul 28, 2026
Merged

web: download U-Boot through OpenIPC's own cors-proxy worker#118
widgetii merged 1 commit into
masterfrom
feature/openipc-cors-proxy

Conversation

@widgetii

Copy link
Copy Markdown
Member

Fixes #113 — properly this time.

Important

This only fixes the deployed site once CORS_PROXY_HMAC_KEY is added to this repo's Actions secrets (same value as the worker's HMAC_SECRET). Until then the deployed origin sends unsigned requests, the worker replies 400, 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 to release-assets.githubusercontent.com transparently.

Verified against the live worker:

Check Result
/health {"status":"ok"}
Proxy the GitHub asset 200, 256459 B, sha256 0160bbcb… — byte-exact
Origin: https://evil.example 403 origin_not_allowed
openipc.github.io, unsigned 400 missing_params

Two things made it drop in cleanly:

  1. No worker change needed. defib is served from openipc.github.io/defib/ — the same origin as openipc.github.io/rnd-player/ — so it already satisfies ALLOWED_ORIGINS.
  2. Local dev needs no key. The worker exempts localhost from HMAC.

Key handling

defib has no bundler (Pages uploads web/ verbatim), so pages.yml writes web/config.js from the CORS_PROXY_HMAC_KEY secret at deploy time — mirroring rnd-player's Vite define and keeping the key out of the repo. The committed config.js has 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 actual src/auth.ts and verify against it:

true                      fresh signature accepted
true                      previous-window signature accepted
false signature_expired   stale timestamp rejected
false invalid_signature   tampered url rejected
false invalid_signature   wrong key rejected

Source order

1. github.com direct       — self-heals if GitHub restores CORS
2. OpenIPC cors-proxy      — the workhorse
3. cors.eu.org / allorigins / codetabs  — threadbare safety net

Every candidate is still SHA-256 verified against the api.github.com digest, 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):

origin           : http://localhost:8932
config loaded    : https://cors-proxy.joseph-nef.workers.dev
button           : Re-download
status           : ✓ 250 KB
bytes            : 256459
sha256           : 0160bbcb7b8e40a13abbc96d34bc26575534e0ea0d6cc4a5b947ce0bd55ce3dc

--- log panel ---
Downloaded u-boot-gk7205v300-universal.bin via OpenIPC cors-proxy — SHA-256 verified

The direct attempt is blocked by CORS first, then the worker succeeds — the exact failure path from the issue, now recovering.

  • 11 new tests; 83 JS pass, 555 Python pass
  • pages.yml validated as YAML; injection step simulated with the secret both set and unset

Follow-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

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>
@widgetii
widgetii merged commit ccfe901 into master Jul 28, 2026
13 checks passed
@widgetii
widgetii deleted the feature/openipc-cors-proxy branch July 28, 2026 13:58
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.

Web version: "Download U-Boot for..." always fails with a CORS error after GitHub redirects

1 participant