test(browse): align dual-listener and terminal-agent static guards with the current source

Two static-grep guards pinned superseded source shapes and failed once the
suite actually ran them: the tunnel dispatch gate is args-aware since the
--out disk-write ban (canDispatchOverTunnel takes command AND args), and
lazy PTY spawn routes through the maybeSpawnPty helper since v1.44. The
updated assertions pin the current, stricter shapes (open() never spawns;
the helper is the only spawnClaude caller).

Contributed by @time-attack (PR #2230, dual-listener + terminal-agent hunks).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan 2026-08-14 18:47:46 -07:00
parent 6694490c07
commit 3f176d2226
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 22 additions and 4 deletions

View File

@ -220,7 +220,9 @@ describe('/command tunnel command allowlist', () => {
'return handleCommand(body, tokenInfo)'
);
expect(commandBlock).toContain("surface === 'tunnel'");
expect(commandBlock).toContain('canDispatchOverTunnel(body?.command)');
// Args-aware since the --out (disk write) tunnel ban: the dispatch gate
// takes both the command and its args.
expect(commandBlock).toContain('canDispatchOverTunnel(body?.command, body?.args)');
expect(commandBlock).toContain('disallowed_command');
expect(commandBlock).toContain('is not allowed over the tunnel surface');
expect(commandBlock).toContain('status: 403');

View File

@ -144,17 +144,33 @@ describe('Source-level guard: terminal-agent', () => {
test('lazy spawn: claude PTY is spawned in message handler, not on upgrade', () => {
// The whole point of lazy-spawn (codex finding #8) is that the WS
// upgrade itself does NOT call spawnClaude. Spawn happens on first
// message frame.
// upgrade itself does NOT spawn claude. Spawn happens on first
// message frame (binary input or the v1.44 explicit `start` frame),
// routed through the maybeSpawnPty helper, which is the only caller
// of spawnClaude.
const upgradeBlock = AGENT_SRC.slice(
AGENT_SRC.indexOf("if (url.pathname === '/ws')"),
AGENT_SRC.indexOf("websocket: {"),
);
expect(upgradeBlock).not.toContain('spawnClaude(');
expect(upgradeBlock).not.toContain('maybeSpawnPty(');
// Spawn must be invoked from the message handler (lazy on first byte).
const messageHandler = AGENT_SRC.slice(AGENT_SRC.indexOf('message(ws, raw)'));
expect(messageHandler).toContain('spawnClaude(');
expect(messageHandler).toContain('maybeSpawnPty(');
expect(messageHandler).toContain('!session.spawned');
// The open() upgrade handler must not spawn — it only creates the
// (spawned: false) session record or re-attaches a detached one.
const openBlock = AGENT_SRC.slice(
AGENT_SRC.indexOf('open(ws)'),
AGENT_SRC.indexOf('message(ws, raw)'),
);
expect(openBlock).not.toContain('spawnClaude(');
expect(openBlock).not.toContain('maybeSpawnPty(');
// And the helper itself is where spawnClaude actually happens, gated
// on session.spawned so it stays a single-shot lazy spawn.
const helperBlock = AGENT_SRC.slice(AGENT_SRC.indexOf('function maybeSpawnPty'));
expect(helperBlock).toContain('spawnClaude(');
expect(helperBlock).toContain('if (session.spawned) return true;');
});
test('process.on uncaughtException + unhandledRejection handlers exist', () => {