Skip to content

Fix Browse local auth leak and migrate legacy profiles#2226

Open
time-attack wants to merge 2 commits into
garrytan:mainfrom
time-attack:harden-pr-env-health-test
Open

Fix Browse local auth leak and migrate legacy profiles#2226
time-attack wants to merge 2 commits into
garrytan:mainfrom
time-attack:harden-pr-env-health-test

Conversation

@time-attack

Copy link
Copy Markdown

Summary

  • Make /health status-only and reject DNS-rebinding-style local Host headers.
  • Give the root credential only to the bundled extension's fixed, verified Chrome identity through private extension storage; content scripts never receive it.
  • Require that same extension identity for terminal WebSocket access while preserving the existing sidebar terminal, pair-agent, and Browse flows.
  • Automatically migrate legacy path-derived extension state whenever setup/updater installation runs, with a private rollback backup and safe deferral while GStack Browser is open.
  • Detect and repair incomplete Playwright browser caches so upgraded legacy installs can still launch a working browser and terminal agent.

This PR intentionally contains no version bump or changelog entry.

What happened, in simple terms

GStack Browse runs a local server on the developer's Mac. The old version put the server's master key in its public /health response. A normal website could not simply read that response because of the browser's same-origin rules, but an unrelated Chrome extension with localhost access could. A website using DNS rebinding was another route that needed to be closed.

That master key was more than a health-check token: after obtaining it, a caller could ask Browse for a terminal session and connect to the local Claude terminal. We reproduced that complete chain with a separate attacker extension and a real terminal WebSocket.

This PR removes the key from HTTP discovery entirely. The Browse process now writes a fresh credential directly into storage owned by one fixed GStack extension ID. Public pages and content scripts cannot request it, the terminal accepts only that exact extension origin, and local HTTP rejects unexpected Host headers.

Automatic migration

Existing installations are repaired without manual profile surgery. The normal setup/updater installation path detects the old path-derived extension identity inside GStack's dedicated Chromium profile, moves only its extension storage into a private rollback directory, and leaves unrelated extensions and the user's normal Chrome profile untouched. The migration is idempotent and defers instead of editing an active profile.

Test Coverage

New regression coverage exercises status-only health responses, forged extension Origins, DNS rebinding Host headers, fixed extension identity derivation, content-script credential isolation, both supported extension launch modes, migration/backup/idempotency behavior, setup wiring, and standards-compliant terminal WebSocket authentication.

Pre-Landing Review

Three independent read-only reviews covered auth/trust boundaries, migration/repair safety, and browser/terminal compatibility. The first pass found five blockers: content scripts could read local extension storage, one extension launch mode lacked a real service worker, live Chromium locks could be missed, invalid profiles could be marked migrated, and repaired Playwright caches lacked the registry marker. All five were fixed and regression-tested. The final re-review found no remaining P0/P1 issues.

Design Review

No visual behavior changed; design review skipped.

Eval Results

No prompt-related files changed; evals skipped.

Plan Completion

No plan file detected.

Documentation

  • ARCHITECTURE.md: documents status-only /health, Host validation, and trusted extension credential provisioning.
  • BROWSER.md: explains the secure extension identity, automatic legacy-profile migration, repair opt-out, and corrected SSE authentication claim.
  • docs/REMOTE_BROWSER_ACCESS.md: clarifies that /health never returns credentials.
  • docs/designs/GSTACK_BROWSER_V0.md: updates the shipped authentication design.

No version history was added.

Verification Results

  • Legacy attack proof: a separate real Chrome extension read the root credential, minted a PTY session, and opened a real terminal WebSocket.
  • Hardened attack proof: the same extension got no credential, could not mint a PTY session, and could not open the terminal WebSocket.
  • Migration cohort: launched Browse against eight public repositories on the legacy build, confirmed the exposure, migrated the shared legacy profile, then launched and navigated twelve fresh public repositories on the hardened build with the credential absent.
  • Browser runtime: the default Playwright cache was repaired and a headed Chromium smoke test passed without a custom executable override.
  • Real trusted-extension test: both headed and off-screen persistent launch modes received and rotated credentials, made an authenticated request, and denied a content-script read.
  • The configured bun run test command completed successfully; the final focused security, migration, browser-manager, and terminal run passed 18/18 tests.

Test plan

  • Reproduce the complete legacy credential-to-terminal attack with a separate Chrome extension.
  • Repeat the same attack against the hardened server and confirm all stages fail.
  • Exercise eight legacy public repositories, run the automatic profile migration, and verify twelve fresh public repositories with real Chromium.
  • Verify fixed extension provisioning, token rotation, content-script isolation, terminal origin checks, and WebSocket protocol compatibility.
  • Run the full configured test suite and focused security/migration regressions.

Fixes #1324

@trunk-io

trunk-io Bot commented Jul 10, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@time-attack time-attack marked this pull request as ready for review July 10, 2026 19:59
@time-attack

Copy link
Copy Markdown
Author

@16francej

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.

Possible Critical Security Issue

1 participant