You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
Introduce a CORS-bypassing fetch proxy that the Studio frontend can use to resolve arbitrary remote resources. The proxy is a small edge-compute service (Cloudflare Worker or equivalent) that authenticates each request against Studio via a short-lived signed token, fetches the target URL on behalf of the user, and streams the bytes back with permissive CORS. Coordinates work across Studio (token-issuing endpoint, JWKS, frontend client) and a separate infrastructure repository for the Worker itself.
Initial motivating use case: remote image URLs pasted into the TipTap editor. Likely consumers also: LTI Thin Cartridges, IMSCP/SCORM packages, H5P, and non-self-contained HTML5 apps.
Complexity: Medium Target branch: unstable
Scope
Included:
Edge-compute Worker service that performs proxied fetches against an allowlist of schemes, with size and rate caps and SSRF-style host denylist.
Per-request authentication: short-lived JWT signed by Studio (asymmetric — RS256/ES256/EdDSA) including a target-URL binding claim (tu = SHA256(url)), matching the pattern Studio already uses for GCS upload URLs.
Studio backend endpoints: token-issuing endpoint (requires session auth, returns 60-second JWT) and /.well-known/jwks.json for the Worker to fetch public keys.
Studio frontend client (fetchViaProxy(url)) that handles token acquisition/caching and wraps the Worker call.
Integration with the existing GCS upload pipeline. The proxy is kept separate because some downstream consumers (archive contents — LTI, SCORM, H5P, non-self-contained HTML5) need to fetch individual files and then rebuild the archive before uploading; direct fetch-to-GCS would short-circuit that.
Other consumers beyond the TipTap paste case. Each (LTI Thin Cartridge ingest, SCORM ingest, H5P, etc.) is its own downstream issue once the proxy is in place.
Strategy
Work spans two repositories — Studio (backend endpoints + frontend client) and the existing infrastructure repo (Worker code). Roughly in dependency order:
Studio backend. Issue a signing keypair (kept in env / KMS, never in the repo), implement the JWKS endpoint, implement the token-issuing endpoint. Token claims at minimum: sub (user id), aud (Worker hostname), iat, exp (~60s), tu (target URL hash).
Worker (in the infrastructure repo). On startup, fetches JWKS from Studio and caches. Per request: verifies the JWT, checks tu matches the requested URL hash, enforces scheme allowlist (https only), host denylist (RFC1918, link-local, localhost), size cap, response content-type allowlist. Streams response back with appropriate CORS headers.
Studio frontend.fetchViaProxy(url) client that obtains a token, calls the Worker, returns a Response. First consumer (TipTap remote image paste) wired up in the same change or a follow-up.
Per-request validation against Studio is intentional — same shape as the GCS upload-URL flow, where each upload is authorized one-at-a-time. Token-issuing endpoint should be optimized to keep latency tight (cache the user's session lookup, sign quickly).
Key rotation: Studio publishes new public keys at JWKS before retiring old ones; Worker honors a short cache TTL (~10 minutes) so rotation propagates without redeploy.
Acceptance Criteria
Studio backend has a token-issuing endpoint that requires session auth and returns a short-lived JWT including the tu (target URL hash) claim.
Studio backend exposes a JWKS endpoint serving the current and previous-rotation public keys.
Worker is deployed and reachable; verifies JWT signature against JWKS, validates tu, enforces scheme allowlist, host denylist, size cap, and response content-type allowlist.
Studio frontend has a fetchViaProxy(url) client used by at least one consumer.
Key rotation procedure documented in the infrastructure repo's README or runbook.
Security review completed before the Worker is exposed to production traffic.
AI usage
Used Claude (Opus 4.7) to draft this issue and walk through the design space (CORS bypass options, JWT validation patterns). The author made the architectural decisions — JWT with target-URL binding, per-request validation against Studio, keeping the proxy separate from the GCS upload pipeline, Tracking-issue framing — and reviewed each section.
❌ This issue is not open for contribution. Visit Contributing guidelines to learn about the contributing process and how to find suitable issues.
Overview
Introduce a CORS-bypassing fetch proxy that the Studio frontend can use to resolve arbitrary remote resources. The proxy is a small edge-compute service (Cloudflare Worker or equivalent) that authenticates each request against Studio via a short-lived signed token, fetches the target URL on behalf of the user, and streams the bytes back with permissive CORS. Coordinates work across Studio (token-issuing endpoint, JWKS, frontend client) and a separate infrastructure repository for the Worker itself.
Initial motivating use case: remote image URLs pasted into the TipTap editor. Likely consumers also: LTI Thin Cartridges, IMSCP/SCORM packages, H5P, and non-self-contained HTML5 apps.
Complexity: Medium
Target branch: unstable
Scope
Included:
tu = SHA256(url)), matching the pattern Studio already uses for GCS upload URLs./.well-known/jwks.jsonfor the Worker to fetch public keys.fetchViaProxy(url)) that handles token acquisition/caching and wraps the Worker call.Excluded:
Strategy
Work spans two repositories — Studio (backend endpoints + frontend client) and the existing infrastructure repo (Worker code). Roughly in dependency order:
sub(user id),aud(Worker hostname),iat,exp(~60s),tu(target URL hash).tumatches the requested URL hash, enforces scheme allowlist (httpsonly), host denylist (RFC1918, link-local,localhost), size cap, response content-type allowlist. Streams response back with appropriate CORS headers.fetchViaProxy(url)client that obtains a token, calls the Worker, returns aResponse. First consumer (TipTap remote image paste) wired up in the same change or a follow-up.Per-request validation against Studio is intentional — same shape as the GCS upload-URL flow, where each upload is authorized one-at-a-time. Token-issuing endpoint should be optimized to keep latency tight (cache the user's session lookup, sign quickly).
Key rotation: Studio publishes new public keys at JWKS before retiring old ones; Worker honors a short cache TTL (~10 minutes) so rotation propagates without redeploy.
Acceptance Criteria
tu(target URL hash) claim.tu, enforces scheme allowlist, host denylist, size cap, and response content-type allowlist.fetchViaProxy(url)client used by at least one consumer.AI usage
Used Claude (Opus 4.7) to draft this issue and walk through the design space (CORS bypass options, JWT validation patterns). The author made the architectural decisions — JWT with target-URL binding, per-request validation against Studio, keeping the proxy separate from the GCS upload pipeline, Tracking-issue framing — and reviewed each section.