What happens
grapharc serve --live-root guards every /live route with a token, and the check (grapharc/server/live.py, _authorized) accepts it two ways: an Authorization: Bearer header, or ?token=... in the query string.
The query-string path exists for a real reason — a browser EventSource cannot set headers, so the SSE stream at /live/api/stream has no other way to authenticate from the live page. But a token in a URL is copied everywhere the URL goes: access logs, uvicorn's default request line, browser history, referrer headers if the page ever links out, and anything a proxy in front logs.
Why it matters
The live view is exactly the feature an operator binds beyond localhost (that is why --live-token and the non-localhost startup warning exist). The moment it is exposed, the secret protecting read access to every trace under the live root starts accruing copies in places with much weaker access control than the traces themselves.
What to consider
- Keep the query parameter for the SSE route only, and strip it from logging: accept
?token= solely on /live/api/stream, and reject it (401, distinct reason) on the HTML and /live/api/runs routes, which are fetched by a browser that can send cookies or by curl that can send headers.
- Or switch the page to a short-lived stream ticket:
/live/view (header-authenticated) mints a one-time token bound to the trace path with a small TTL, and the page passes that to EventSource instead of the long-lived secret.
- At minimum, document the exposure in the cookbook page that introduces
--live-token, so the operator binding to 0.0.0.0 behind nginx knows to scrub query strings from access logs.
Out of scope
Cookie/session auth for the whole server, and anything about the main API's authentication story — this is only about not writing the live secret into logs.
Acceptance criteria
A request with the token in the query string to a non-SSE /live route is refused; the SSE route still works from the shipped page; the cookbook names the tradeoff.
What happens
grapharc serve --live-rootguards every/liveroute with a token, and the check (grapharc/server/live.py,_authorized) accepts it two ways: anAuthorization: Bearerheader, or?token=...in the query string.The query-string path exists for a real reason — a browser
EventSourcecannot set headers, so the SSE stream at/live/api/streamhas no other way to authenticate from the live page. But a token in a URL is copied everywhere the URL goes: access logs, uvicorn's default request line, browser history, referrer headers if the page ever links out, and anything a proxy in front logs.Why it matters
The live view is exactly the feature an operator binds beyond localhost (that is why
--live-tokenand the non-localhost startup warning exist). The moment it is exposed, the secret protecting read access to every trace under the live root starts accruing copies in places with much weaker access control than the traces themselves.What to consider
?token=solely on/live/api/stream, and reject it (401, distinct reason) on the HTML and/live/api/runsroutes, which are fetched by a browser that can send cookies or by curl that can send headers./live/view(header-authenticated) mints a one-time token bound to the trace path with a small TTL, and the page passes that toEventSourceinstead of the long-lived secret.--live-token, so the operator binding to0.0.0.0behind nginx knows to scrub query strings from access logs.Out of scope
Cookie/session auth for the whole server, and anything about the main API's authentication story — this is only about not writing the live secret into logs.
Acceptance criteria
A request with the token in the query string to a non-SSE
/liveroute is refused; the SSE route still works from the shipped page; the cookbook names the tradeoff.