From b8d9e0987b471b4d2a897647a05aaa19023c18f8 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 4 Jun 2026 08:17:51 +0200 Subject: [PATCH] fix(agents): bound runtime tool list projection --- src/agents/tool-schema-projection.test.ts | 26 +++++++++++++++++++++++ src/agents/tool-schema-projection.ts | 23 +++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/agents/tool-schema-projection.test.ts b/src/agents/tool-schema-projection.test.ts index 318530b0e760..a66a8f89cdf4 100644 --- a/src/agents/tool-schema-projection.test.ts +++ b/src/agents/tool-schema-projection.test.ts @@ -134,6 +134,32 @@ describe("runtime tool input schema projection", () => { }); }); + it("reports invalid runtime tool list lengths", () => { + const healthy = { + name: "healthy", + parameters: { type: "object", properties: {} }, + }; + const proxy = new Proxy([healthy] as Array, { + get(target, property, receiver) { + if (property === "length") { + return -1; + } + return Reflect.get(target, property, receiver); + }, + }); + + expect(filterRuntimeCompatibleTools(proxy)).toEqual({ + tools: [], + diagnostics: [ + { + toolName: "tool[0]", + toolIndex: 0, + violations: ["runtime tool list length is invalid"], + }, + ], + }); + }); + it("quarantines unreadable runtime tool fields without dropping healthy siblings", () => { const unreadable = { name: "fuzzplugin_unreadable", diff --git a/src/agents/tool-schema-projection.ts b/src/agents/tool-schema-projection.ts index 3e10adc7b849..666f97044811 100644 --- a/src/agents/tool-schema-projection.ts +++ b/src/agents/tool-schema-projection.ts @@ -41,15 +41,18 @@ type RuntimeToolEntryRead> { return { ok: false, diagnostic: { toolName: `tool[${toolIndex}]`, toolIndex, - violations: [`tool[${toolIndex}] is unreadable`], + violations: [violation], }, }; } @@ -63,6 +66,24 @@ function readRuntimeToolEntries]; } + if (!Number.isSafeInteger(length) || length < 0) { + return [ + unreadableRuntimeToolEntry( + 0, + "runtime tool list length is invalid", + ) as RuntimeToolEntryRead, + ]; + } + // Projection is a safety check for plugin-controlled tool lists. Reject + // hostile array-like lengths before diagnostics become an unbounded loop. + if (length > MAX_RUNTIME_TOOL_ENTRY_READS) { + return [ + unreadableRuntimeToolEntry( + MAX_RUNTIME_TOOL_ENTRY_READS, + `runtime tool list length exceeds ${MAX_RUNTIME_TOOL_ENTRY_READS}`, + ) as RuntimeToolEntryRead, + ]; + } const entries: RuntimeToolEntryRead[] = []; for (let toolIndex = 0; toolIndex < length; toolIndex += 1) { try {