Skip to content

server: the /live token is accepted in the query string, where it outlives the request in logs and history #41

Description

@Shashankss1205

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions