small gleam coding and persistent agent daemon with a detachable cli and a canvas ui albedo.sh

Vite HTTP proxy strips Origin without validation, allowing cross-origin bodyless mutations #165

Closed okami.mom
Closed opened by okami.mom

Summary #

Vite's ordinary HTTP daemon proxy strips the browser's Origin header without validating it first, while supplying the daemon bearer token. This permits cross-origin bodyless mutations through the development server, including from a sandboxed artifact with known target IDs.

Scope: npm run dev, not the production albedo web proxy.

Reviewed commit: 154c6911d6a491aeeecacf26a06b6ae8c4bc70e1.

Code path #

  • web/vite.config.ts:19–24: stripBrowserHeaders removes Origin, Cookie and Referer, and is installed unconditionally for ordinary proxyReq.
  • The same helper's proxyReqWs path checks Origin first (:25–32), but HTTP does not.
  • /api uses this token-injecting proxy (:80).
  • The production proxy checks Origin in cli/internal/webui/server.go before forwarding authenticated API traffic.
  • src/albedo/harness/extensions/run/service.gleam:122–137 accepts a bodyless POST to stop a job, via api.empty_body(req), then performs the stop.

Concrete request to validate #

From an opaque-origin sandboxed artifact on the development server, with a known session/job pair:

fetch('/api/extensions/run/sessions/<session>/jobs/<job>/stop', {
  method: 'POST',
  mode: 'no-cors',
});

This is a simple empty POST, requiring no JSON preflight. Browser CORS can prevent the caller from reading the response, but does not prevent sending the request or its server-side effect. The proxy removes the Origin that would otherwise let the daemon reject it, and authenticates the forwarded request itself.

The target IDs are a prerequisite: this report does not establish enumeration or unrestricted readable API access from an opaque origin. A sandboxed artifact intentionally has network access, but the documented artifact boundary says it must not reach albedo's API.

Validation status #

Source-traced only; no live browser or Vite reproduction was performed. Please verify with a private test daemon and a harmless counted/observable mutation before assigning final severity.

Expected behavior #

Validate the browser Origin on ordinary HTTP requests before stripping it, consistently with the production boundary and the existing WebSocket check. Test rejection of Origin: null and foreign origins on a bodyless mutation; response-only CORS policy is insufficient.

Fixed in fca0701e: the dev server's /api proxy refuses a foreign or null Origin (403) before it adds the daemon token, using the same rule as its WebSocket path. Reproduced first against a private daemon: a bodyless POST stopping a running job went through with Origin null and a foreign origin, and is now refused. Test: web/scripts/dev-proxy.test.mjs.

Sign up or Sign in to add to the discussion.