fix: remove auth token from /health, secure extension bootstrap (CRITICAL-02 + HIGH-03)

- Remove token from /health response (was leaked to any localhost process)
- Write .auth.json to extension dir for Manifest V3 bootstrap
- sidebar-agent reads token from state file via BROWSE_STATE_FILE env var
- Remove getToken handler from extension (token via health broadcast)
- Extension loads token before first health poll to prevent race condition
This commit is contained in:
Garry Tan
2026-03-27 22:13:45 -07:00
parent 11695e3aca
commit e16bf23ca0
6 changed files with 114 additions and 40 deletions
+26 -1
View File
@@ -211,7 +211,7 @@ export class BrowserManager {
* The browser launches headed with a visible window — the user sees
* every action Claude takes in real time.
*/
async launchHeaded(): Promise<void> {
async launchHeaded(authToken?: string): Promise<void> {
// Clear old state before repopulating
this.pages.clear();
this.refMap.clear();
@@ -223,6 +223,17 @@ export class BrowserManager {
if (extensionPath) {
launchArgs.push(`--disable-extensions-except=${extensionPath}`);
launchArgs.push(`--load-extension=${extensionPath}`);
// Write auth token for extension bootstrap (read via chrome.runtime.getURL)
if (authToken) {
const fs = require('fs');
const path = require('path');
const authFile = path.join(extensionPath, '.auth.json');
try {
fs.writeFileSync(authFile, JSON.stringify({ token: authToken }), { mode: 0o600 });
} catch (err: any) {
console.warn(`[browse] Could not write .auth.json: ${err.message}`);
}
}
}
// Launch headed Chromium via Playwright's persistent context.
@@ -751,6 +762,20 @@ export class BrowserManager {
if (extensionPath) {
launchArgs.push(`--disable-extensions-except=${extensionPath}`);
launchArgs.push(`--load-extension=${extensionPath}`);
// Write auth token for extension bootstrap during handoff
if (this.serverPort) {
try {
const { resolveConfig } = require('./config');
const config = resolveConfig();
const stateFile = path.join(config.stateDir, 'browse.json');
if (fs.existsSync(stateFile)) {
const stateData = JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
if (stateData.token) {
fs.writeFileSync(path.join(extensionPath, '.auth.json'), JSON.stringify({ token: stateData.token }), { mode: 0o600 });
}
}
} catch {}
}
console.log(`[browse] Handoff: loading extension from ${extensionPath}`);
} else {
console.log('[browse] Handoff: extension not found — headed mode without side panel');