fix(shared,host-service): constrain proxy/tunnel upstream before attaching the host secret (GHSA-8qrv-53x7-hh4p, GHSA-h894-rmgp-jv6m)

Merged
KI
1mo ago
+437-15

Summary

The sandbox gate built its upstream from the request path, so a // path forwarded the workspace host secret to an arbitrary origin; the tunnel client could send HOST_SERVICE_SECRET to a relay-selected origin. A bypassable relay-side check was also found.

Fix (well-factored)

New shared packages/shared/src/upstream-url.ts guard constrains the origin before the secret is attached; gate, tunnel client, and the relay's bespoke check all consolidated onto it.

Proof

Gate exploit forwarded the real Bearer secret to https://evil.example pre-fix; blocked post-fix. Tests: gate 2 pass/5 fail → 8/0, tunnel 4 fail → 19/0, shared 1114 pass; typecheck 8/8; biome + sherif clean. One client-side reachability sub-claim is PARTIAL. Pre-existing unrelated failure verified on a clean tree.
Write-up: https://app.superset.sh/page/p1-b-host-secret-disclosure-to-an-attacker-selecte-yhe08o

Merge note

Squash-merge only (earlier commits held local repro artifacts, now untracked).


Summary by cubic

Fixes a host-secret disclosure where a crafted request target could move the upstream origin and carry the host secret with it, in the sandbox gate (GHSA-8qrv-53x7-hh4p) and the tunnel client (GHSA-h894-rmgp-jv6m).

  • The gate resolved its upstream via new URL(path, claims.target), so a protocol-relative // path replaced the ticket target and sent the workspace secret to the attacker's host; the tunnel client concatenated a relay-supplied path onto the loopback origin, where a leading @ turned that origin into userinfo and HOST_SERVICE_SECRET followed, past the relay's startsWith("//") check, which missed tab- and newline-smuggled protocol-relative forms.
  • All three sites now use the shared resolveUpstreamUrl/isOriginFormTarget in packages/shared/src/upstream-url.ts, which refuse anything not in origin-form and re-check the resolved origin against the base before any secret is attached; refusing call sites return 400 instead of proxying.

Summary by CodeRabbit

  • Bug Fixes
    • Requests with malformed or off-origin paths are now rejected instead of being forwarded to an unintended destination.
    • Invalid ticket targets now receive a clear 400 response.
  • Tests
    • Added coverage for request forwarding, WebSocket handling, and invalid path and target scenarios.