test: pin the no-recursive-run_code invariant; document the fold at the drain site (bot review)

Both bot criticals verified against the code and rejected as exploit
paths — pinned instead of patched:

The bindings loop already excludes run_code (the skip predates the
finding), and the runtime host resolves forged port calls as own
properties of the bindings record, so an absent binding is unreachable
from a program under any mode. A new both-mode test pins the invariant:
the record has no run_code key on any lookup path.

The drain await cannot mask a run failure: `queue` is the folded tail
(every link swallows its rejection), so `await queue` never rejects and
the runtime's own result.error always reaches the CodeRunFailedError
conversion — the existing abort test exercises exactly the
queued-abandonment-plus-run-failure scenario. Stated at the drain site so
the fold's purpose is explicit.
This commit is contained in:
Tianyi Cui
2026-07-08 14:11:15 +08:00
parent 84088300bc
commit d7a27b20df
2 changed files with 24 additions and 0 deletions

View File

@@ -259,6 +259,10 @@ export function createRunCodeTool(registry: ToolRegistry, requireRuntime: () =>
// an in-flight sub-dispatch, abandoning queued ones), then await the
// queue's drain — an aborted sub-call still settles and logs its
// event INSIDE the open turn; nothing can append after we return.
// `queue` is the FOLDED tail (every link swallows its rejection into
// undefined), so this await cannot itself reject — an abandoned
// queued call can never mask the runtime's own `result.error` below;
// rejections surface only on the per-call promises the program holds.
runController.abort('run_code settled')
await queue

View File

@@ -119,6 +119,26 @@ describe('mode-aware wire contribution', () => {
expect(assembly.sections.some(section => section.name === 'tools:sdk')).toBe(true)
})
it("never exposes run_code to programs, even under mode 'both' (no recursive dispatch path)", async () => {
const { ctx, runtime } = await setup({ mode: 'both' })
registerEcho(ctx)
runtime.behavior = (request) => {
const functions = request.bindings[0]!.functions
return Promise.resolve({
logs: [],
value: JSON.stringify({
names: Object.keys(functions).sort(),
// Own-property AND prototype-chain reads both come back empty —
// there is no handle a program could re-enter run_code through.
runCode: String(functions[RUN_CODE_NAME]),
}),
})
}
const result = await runCode(ctx, 'program')
expect(result.isError).toBe(false)
expect(JSON.parse((result.content[0] as { text: string }).text)).toEqual({ names: ['echo'], runCode: 'undefined' })
})
it('renders byte-identical SDK text across consecutive assemblies of an unchanged tool set', async () => {
const { ctx, systemPrompt } = await setup({ mode: 'code' })
registerEcho(ctx)