Skip to content

[security] Code bridge pages inherit portal-origin authority without Code origin isolation #725

Description

@coygeek

[security] Code bridge pages inherit portal-origin authority without Code origin isolation

Summary

When CRABBOX_CODE_ORIGIN_TEMPLATE is unset or invalid, Crabbox keeps Code bridge traffic on the coordinator origin under /portal/leases/{lease}/code/.... The proxied Code response is lease-controlled HTML/JavaScript, but it is returned with a CSP that permits scripts and same-origin connections. Because portal sessions use a crabbox_session cookie scoped to Path=/, JavaScript running in that Code page can make same-origin requests to other /portal routes as the viewer.

This crosses the supported coordinator authentication and ownership boundary described in SECURITY.md: the issue does not require Crabbox to isolate hostile arbitrary tenants, but it does let lower-trust lease-controlled browser content act with the authenticated portal identity of a user or admin who opens that lease's Code bridge during supported sharing or inspection. The maintained operations guide documents per-lease Code origins as optional defense-in-depth for exactly this cooperative sharing/admin-inspection surface, but the default same-origin compatibility path leaves the ambient-authority path reachable.

Affected Components

  • Checked commit: 5001cd1179a42c174da65c8e8a87bf16218aef95
  • Component: coordinator portal / Code bridge
  • Affected files and lines:
    • worker/src/fleet.ts:4881-4882 routes /portal/leases/{id}/code/... into the Code proxy.
    • worker/src/fleet.ts:7029-7105 proxies Code traffic on the portal origin unless a configured isolated origin exists.
    • worker/src/code-origin.ts:7-35 returns no isolated origin when CRABBOX_CODE_ORIGIN_TEMPLATE is missing, malformed, non-HTTPS, or lacks exactly one {lease} placeholder.
    • worker/src/fleet.ts:13254-13301 replaces upstream CSP with a VS Code-compatible policy that allows script execution and same-origin connect-src.
    • worker/src/coordinator-entry.ts:86-106 authenticates /portal... requests by converting the portal cookie into an internal bearer request.
    • worker/src/coordinator-entry.ts:191-201 reads crabbox_session from the request cookie and sets Authorization: Bearer ... for portal requests.
    • worker/src/oauth.ts:660-668 mints the portal cookie with Path=/.

Attack Path

Attacker-controlled source:

Lease-controlled Code bridge HTTP responses, such as code-server HTML, extension-provided web content, or other script-capable content returned through the Code bridge for a lease visible to the victim.

Relevant control:

codeOriginForLease() returns undefined when CRABBOX_CODE_ORIGIN_TEMPLATE is absent or invalid. In that state, codePortalProxy() does not redirect the browser to a separate per-lease origin; it continues serving Code responses from the coordinator's /portal/leases/{lease}/code/... path.

Sink:

The victim browser executes the lease-controlled Code page as coordinator-origin JavaScript. Same-origin requests to /portal include the Path=/ crabbox_session cookie, and prepareCoordinatorRequest() converts that cookie into authenticated portal authority.

Preconditions:

  • The coordinator has Code bridge support enabled for a lease and does not have a valid per-lease CRABBOX_CODE_ORIGIN_TEMPLATE.
  • The victim can access the attacker's Code-capable lease through normal portal visibility, sharing, or admin inspection.
  • The victim opens the lease's Code bridge page while authenticated to the portal.

Steps:

  1. The victim authenticates to the coordinator portal and receives a crabbox_session cookie scoped to Path=/.
  2. The victim opens /portal/leases/{attacker-lease}/code/....
  3. Because no isolated Code origin is available, codePortalProxy() returns the Code bridge response directly under the coordinator origin.
  4. The response CSP allows scripts and same-origin network requests, so lease-controlled JavaScript can call portal routes such as /portal, /portal/admin for admin viewers, /portal/runs/{run-id}/logs, /portal/leases/{victim-lease}/share?format=json, or POST actions such as /portal/leases/{victim-lease}/share and /portal/leases/{victim-lease}/release when the viewer has manage access.
  5. The browser automatically attaches crabbox_session; Crabbox converts that cookie into authenticated portal context and returns or performs the action as the viewer.

Important scope limit:

The portal cookie is not a general /v1 API bearer token in browser JavaScript. prepareCoordinatorRequest() only converts crabbox_session for /portal... requests, and the portal documentation states that /v1/... API tokens are separate and not echoed to the browser. This report is therefore scoped to portal reads and portal actions reachable with the viewer's portal session, not direct arbitrary /v1 API calls.

Impact

A Code-capable lease can become a same-origin script execution surface for the coordinator portal. If another user opens that lease's Code bridge, the lease-controlled page can read portal pages and JSON endpoints visible to that user, including lease metadata, share state, run details, retained logs, and admin portal pages for admin viewers. It can also submit same-origin portal POST actions that the viewer is authorized to perform, such as changing share state or stopping/deleting leases the viewer can manage.

This is reportable under the maintainer-authored scope because the coordinator's authentication, ownership, and sharing controls are explicit product boundaries for the supported trusted-team model. The finding is not based on mutually hostile tenants sharing a broker as an isolation boundary; it is based on Crabbox serving lease-controlled Code HTML/JavaScript on the same browser origin as authenticated coordinator portal controls in a documented sharing/admin-inspection workflow.

Severity Assessment

CVSS Assessment

Metric v3.1 v4.0
Score 9.0 / 10.0 9.3 / 10.0
Severity Critical Critical
Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Calculator CVSS v3.1 Calculator CVSS v4.0 Calculator

The attack is network-reachable for an authenticated lower-privilege user or lease controller and requires user interaction from the victim. Once the victim opens the bridged Code page, execution moves from lease-controlled content into coordinator-origin portal authority, allowing high confidentiality, integrity, and availability impact over the portal-visible resources and actions reachable by the victim.

Recommended Remediation

Fail closed for browser Code bridge traffic unless it is isolated from the coordinator portal origin. Practical options:

  • Require a valid CRABBOX_CODE_ORIGIN_TEMPLATE before serving script-capable Code bridge responses to browsers.
  • If compatibility requires retaining the same entry URL, keep /portal/leases/{id}/code/... as a launcher that mints a short-lived viewer ticket and redirects to a per-lease origin before any Code HTML/JavaScript is served.
  • For deployments that intentionally keep same-origin Code for local compatibility, render only a non-executable interstitial on the coordinator origin and require explicit operator acknowledgement before opening the Code service in an isolated origin or separate host.
  • Add regression tests proving that missing or invalid CRABBOX_CODE_ORIGIN_TEMPLATE cannot silently fall back to coordinator-origin script execution for shared/admin-visible Code pages.

Do not rely on filtering crabbox_session before forwarding requests to the Code backend as the primary fix. That protects the backend from directly receiving the portal cookie, but it does not stop browser-executed same-origin JavaScript from using the cookie against /portal routes.

Validation

Validation rubric:

  • Source: lease-controlled Code HTML/JavaScript reaches the browser through a supported Code bridge route.
  • Control: missing or invalid CRABBOX_CODE_ORIGIN_TEMPLATE produces same-origin fallback instead of fail-closed behavior.
  • Sink: same-origin JavaScript can call authenticated /portal routes with the viewer's crabbox_session.
  • Boundary: the path affects coordinator authentication, ownership, sharing, and admin-inspection surfaces, which SECURITY.md treats as product boundaries.
  • Counterevidence checked: raw /v1 API calls are not authenticated by the portal cookie, and the backend does not receive crabbox_session through proxied Code requests.

Evidence:

  • SECURITY.md:22-25 states that coordinator authentication, ownership, and sharing controls remain product boundaries in the supported trusted-team model. SECURITY.md:38-42 lists authentication bypass, cross-owner access contrary to authorization, and trusted credential exposure to a lower-trust destination as in-scope examples.
  • docs/security.md:43-48 says per-lease Code origins keep lease-controlled Code HTML/JavaScript off the coordinator origin and off other lease origins for cooperative sharing and admin inspection. The same text classifies this as defense-in-depth and not hostile multitenant isolation; the accepted issue is scoped to the coordinator portal authority boundary, not hostile tenant isolation.
  • docs/features/portal.md:58-77 documents the intended isolated-origin flow and explicitly says missing or invalid configuration keeps existing same-origin Code behavior for compatibility.
  • docs/features/portal.md:81-103 documents that portal pages use crabbox_session, that the Worker converts it internally to bearer auth for portal pages, and that /v1 API tokens are separate and not echoed to the browser.
  • worker/src/fleet.ts:4881-4882 routes /portal/leases/{id}/code/... to codePortalProxy().
  • worker/src/code-origin.ts:11-34 returns undefined for missing or invalid templates.
  • worker/src/fleet.ts:7077-7080 redirects to an isolated Code origin only when codeOriginForLease() returns one; otherwise the request proceeds to codeProxyHTTP() at worker/src/fleet.ts:7105.
  • worker/src/fleet.ts:7411-7417 returns the bridged Code HTTP response with codeResponseHeaders().
  • worker/src/fleet.ts:13254-13301 allows script execution and same-origin connections on proxied Code responses.
  • worker/src/coordinator-entry.ts:86-106 authenticates /portal requests, and worker/src/coordinator-entry.ts:191-201 converts the crabbox_session cookie to Authorization: Bearer ... for those portal requests.
  • worker/src/oauth.ts:660-668 sets crabbox_session with Path=/, making it available to same-origin /portal/leases/{id}/code/... and other /portal... requests.
  • worker/src/fleet.ts:5729-5780 demonstrates a same-origin portal JSON/read-write target for lease sharing when the viewer can manage that lease, and worker/src/fleet.ts:5293-5328 demonstrates a portal POST action that releases/deletes manageable leases.
  • worker/test/fleet.test.ts:13498-13524 confirms proxied Code HTML receives a VS Code-compatible CSP and cannot set crabbox_session; worker/test/fleet.test.ts:13542-13552 confirms crabbox_session is not forwarded to the Code backend. Those are useful controls, but they do not prevent browser-side same-origin requests to /portal routes.
  • Local CVSS validation with the cvss Python package returned (9.0, 9.0, 9.1) for CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H and (9.3,) for CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H.

Suggested regression coverage:

  • Add a Worker test that configures a Code-capable lease with no CRABBOX_CODE_ORIGIN_TEMPLATE and asserts browser Code HTML is not served as executable coordinator-origin content.
  • Add a Worker test that invalid templates do not silently preserve executable same-origin Code responses for shared/admin-visible portal paths.
  • Keep existing positive coverage for valid templates, short-lived isolated viewer sessions, path-scoped Code cookies, and backend cookie filtering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal priority bug or improvement with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:auth-providerThis issue is about auth, provider routing, model choice, or SecretRef resolution.impact:securityThis issue is about security boundaries, credentials, authz, sandboxing, or sensitive data.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions