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:stripBrowserHeadersremoves Origin, Cookie and Referer, and is installed unconditionally for ordinaryproxyReq.- The same helper's
proxyReqWspath checks Origin first (:25–32), but HTTP does not. /apiuses this token-injecting proxy (:80).- The production proxy checks Origin in
cli/internal/webui/server.gobefore forwarding authenticated API traffic. src/albedo/harness/extensions/run/service.gleam:122–137accepts a bodyless POST to stop a job, viaapi.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.