fix: cookie picker auth token leak (CVE — CVSS 7.8)

GET /cookie-picker served HTML that inlined the master bearer token
without authentication. Any local process could extract it and use it
to call /command, executing arbitrary JS in the browser context.

Fix: Jupyter-style one-time code exchange. The picker URL now includes
a one-time code that is consumed via 302 redirect, setting an HttpOnly
session cookie. The master AUTH_TOKEN never appears in HTML. The session
cookie is isolated from the scoped token system (not valid for /command).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-07 18:49:45 -10:00
parent 6cc094cd41
commit 360bb2e192
5 changed files with 302 additions and 68 deletions

View File

@@ -317,4 +317,28 @@ describe('Server auth security', () => {
// The ownership check condition must exclude newtab
expect(ownershipBlock).toContain("command !== 'newtab'");
});
// CVE fix: cookie-picker HTML must NOT inline the auth token.
// getCookiePickerHTML() must not accept an authToken parameter.
test('cookie-picker UI does not accept or inline auth token', () => {
const uiSrc = fs.readFileSync(path.join(import.meta.dir, '../src/cookie-picker-ui.ts'), 'utf-8');
// Function signature must not include authToken
expect(uiSrc).not.toMatch(/getCookiePickerHTML\([^)]*authToken/);
// No AUTH_TOKEN interpolation in template
expect(uiSrc).not.toContain("AUTH_TOKEN = '${authToken");
expect(uiSrc).not.toContain("AUTH_TOKEN = '${auth");
});
// CVE fix: cookie-picker route handler uses one-time code exchange, not open access.
test('cookie-picker HTML route requires code or session cookie', () => {
const routeSrc = fs.readFileSync(path.join(import.meta.dir, '../src/cookie-picker-routes.ts'), 'utf-8');
// Must have code validation
expect(routeSrc).toContain('pendingCodes');
expect(routeSrc).toContain('validSessions');
// Must NOT pass authToken to getCookiePickerHTML
expect(routeSrc).not.toMatch(/getCookiePickerHTML\([^)]*authToken/);
// Must set HttpOnly session cookie
expect(routeSrc).toContain('HttpOnly');
expect(routeSrc).toContain('SameSite=Strict');
});
});