fix(browse): adversarial-review hardening — 6 findings fixed, regression-pinned

Pre-push adversarial review (4 lenses, refute-style verification: 13 raw
findings, 7 refuted, 6 confirmed) caught these; each fix carries a pin:

1. --restrict=read (equals form) sailed past validatePairAgentFlags —
   hasFlag/parseFlag are exact-token matches — so the user asked for a
   read-only sandbox and silently got FULL access: the exact failure mode
   this branch claims to close. The equals form is now a hard error before
   any server work.
2. handleTunnel trimmed the agent name but clientIds are stored verbatim,
   so a space-padded agent was unrevocable by the documented kill switch
   (trimmed DELETE 404'd while the grant stayed live). Names now pass
   through verbatim; the live-daemon test revokes ' padded'.
3. The sole pin for "CLI always sends explicit scopes" passed vacuously on
   a simulated revert: toContain('DEFAULT_PAIR_SCOPES') was satisfied by a
   comment. The tripwire now matches the code shape with a regex and bans
   the conditional spread formatting-insensitively.
4. The rewritten 403 scope hint was unpinned — new e2e asserts it names
   --restrict and --control and never --admin.
5. tunnelRevoke's verify-failure and HTTP-error branches and tunnelAgents'
   unreadable-list branch had no coverage — three stub-daemon pins added
   (an unreadable list must never render as "No paired agents").
6. CHANGELOG claimed "40+ new test cases"; the honest count is 35.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-20 04:29:07 +00:00
co-authored by Claude Fable 5
parent 5b22add861
commit 1ae9aef999
5 changed files with 156 additions and 5 deletions
+27
View File
@@ -264,6 +264,33 @@ describe('pair-agent flow end-to-end (HTTP only, no ngrok)', () => {
expect(body.error).toContain('control');
});
test('scope-denied 403 hint points at --restrict/--control, never --admin', async () => {
// Regression: the old hint said "re-pair with --admin", which is a legacy
// alias for --control — following it over-granted browser-wide control.
const pairResp = await fetch(`${daemon.baseUrl}/pair`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${daemon.token}` },
body: JSON.stringify({ clientId: 'hint-agent', scopes: ['read'] }),
});
const { setup_key } = await pairResp.json() as any;
const connectResp = await fetch(`${daemon.baseUrl}/connect`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ setup_key }),
});
const { token } = await connectResp.json() as any;
const resp = await fetch(`${daemon.baseUrl}/command`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ command: 'goto', args: ['https://example.com'] }),
});
expect(resp.status).toBe(403);
const body = await resp.json() as any;
expect(body.hint).toContain('--restrict');
expect(body.hint).toContain('--control');
expect(body.hint).not.toContain('--admin');
});
// ─── Revocation e2e: revoke-all + the /agents verification surface ────
test('DELETE /token revokes session AND setup keys; agent leaves /agents; token 401s; re-connect fails', async () => {