Skip to content

fix(dashboard): authenticate by request header, not a shared cookie - #4301

Merged
ScriptedAlchemy merged 4 commits into
masterfrom
fix/4203-dashboard-header-auth
Oct 18, 2026
Merged

ScriptedAlchemy merged 4 commits into
masterfrom
fix/4203-dashboard-header-auth

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

Fixes #4203

Broken. The dashboard traded its launch token for a Path=/ cookie. Browsers scope cookies by host, not port, so the cookie was replayed to any other service on the same loopback host.

Fix. The dashboard never sets a cookie now. Credential flow: launch URL /?token=T -> 303 /path#token=T (no-store, no-referrer; a fragment never goes on the wire) -> the page moves the token into origin-scoped sessionStorage (the origin includes the port, so concurrent dashboards stay separate), clears the fragment with replaceState, and sends Authorization: Basic on every same-origin request. The event stream is a fetch-based EventSource replacement (dashboard/src/data/auth/) that sends the header and resumes with Last-Event-ID. Host, Origin, token and Basic checks are unchanged. The embedded page shell (/, /static/**, client routes) carries no secret and is now served without the token, since a navigation cannot carry a header. Every /api route still needs it. docs/dashboard.md is updated.

Test choice. A browser fixture would need a browser plus the full binary and bundle, so the regression test is a Rust test with a real second TCP listener and a browser model (host-scoped cookie jar, origin-scoped page storage): authority_tests/credential_isolation.rs. Dashboard-side behavior has vitest coverage.

Evidence.

  • Mutation: re-adding a Set-Cookie to the launch redirect fails an_unrelated_loopback_listener_receives_no_replayable_credential ("the other listener saw the dashboard token: Cookie: tracedecay_dashboard_1=...") and loopback_requests_need_the_listener_access_token.
  • Real binary in Chrome-engine pane, isolated profile: the app loaded and showed data with the stream LIVE, document.cookie was empty, and a snoop listener on another loopback port saw no Cookie or Authorization header after the browser visited it.
  • Passing: //crates/tracedecay-dashboard-api:unit_test, //crates/tracedecay:dashboard_api_test, the two updated mcp_suite tests, clippy and rustfmt for touched crates, dashboard typecheck, full vitest (2106), contracts:check.

Devin Review

@changeset-bot

changeset-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5895bfb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

Comment on lines +105 to +106
let path = uri.path();
matches!(*method, Method::GET | Method::HEAD) && path != "/api" && !path.starts_with("/api/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟥 Non-API routes bypass dashboard authentication

When a listener mounts a GET route outside /api, is_page_shell admits it without a token. The previous admission check protected every non-launch route, so callers can read data exposed there anonymously.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@ScriptedAlchemy
ScriptedAlchemy merged commit c43d6d6 into master Oct 18, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove cross-port leakage of the dashboard's reusable authentication cookie

1 participant