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:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user