Summary
The daemon's same-origin guard (isLocalSameOrigin, origin-validation.ts) treats a
client-supplied Sec-Fetch-Site: same-origin header as sufficient authorization when
the Origin header is absent:
GET /api/app-config -> 403 cross-origin request rejected
GET /api/app-config
-H 'Sec-Fetch-Site: same-origin' -> 200 (full app config)
Sec-Fetch-Site is set by user agents and cannot be forged by JavaScript, but it is
trivially forgeable by any non-browser HTTP client (curl, scripts). The guarded
endpoints (/api/app-config, MCP oauth/install routes, /api/dir-exists, …) therefore
rely on a header any LAN attacker can set.
Impact
With API auth enabled, these endpoints are the only thing between a LAN client and
daemon-config mutation (flipping onboarding state, agent selection, triggering OAuth
install flows). Low severity in practice, but the guard is load-bearing when
OD_API_TOKEN enforcement is on.
Suggested fix
Fail closed: when the request host is not loopback, require an Origin header that
matches OD_ALLOWED_ORIGINS (or valid Bearer auth). Use Sec-Fetch-Site: same-origin
only as a secondary signal for real browsers that omit Origin on same-origin GETs —
never as the sole grant. The case "Origin absent + no Sec-Fetch-Site" from a
non-browser client must be denied.
Summary
The daemon's same-origin guard (
isLocalSameOrigin, origin-validation.ts) treats aclient-supplied
Sec-Fetch-Site: same-originheader as sufficient authorization whenthe
Originheader is absent:Sec-Fetch-Siteis set by user agents and cannot be forged by JavaScript, but it istrivially forgeable by any non-browser HTTP client (curl, scripts). The guarded
endpoints (
/api/app-config, MCP oauth/install routes,/api/dir-exists, …) thereforerely on a header any LAN attacker can set.
Impact
With API auth enabled, these endpoints are the only thing between a LAN client and
daemon-config mutation (flipping onboarding state, agent selection, triggering OAuth
install flows). Low severity in practice, but the guard is load-bearing when
OD_API_TOKENenforcement is on.Suggested fix
Fail closed: when the request host is not loopback, require an
Originheader thatmatches
OD_ALLOWED_ORIGINS(or valid Bearer auth). UseSec-Fetch-Site: same-originonly as a secondary signal for real browsers that omit
Originon same-origin GETs —never as the sole grant. The case "Origin absent + no Sec-Fetch-Site" from a
non-browser client must be denied.