Skip to content

CORS-bypassing fetch proxy for the Studio frontend #5900

@rtibbles

Description

@rtibbles

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.
  • First consumer wired up: remote image URLs pasted into the TipTap editor (currently stripped per Strip <img> tags from pasted HTML in TipTap editor #5897).

Excluded:

  • 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:

  1. 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).
  2. 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.
  3. 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.
  • First consumer integrated: remote image URLs pasted into the TipTap editor are fetched and uploaded as checksum-backed images (replacing today's strip behavior from Strip <img> tags from pasted HTML in TipTap editor #5897).
  • 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.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions