Skip to content

Commit 3ffb360

Browse files
authored
fix(codex): quarantine unreadable dynamic tools (#90022)
1 parent 5c53918 commit 3ffb360

2 files changed

Lines changed: 280 additions & 37 deletions

File tree

extensions/codex/src/app-server/dynamic-tools.test.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,116 @@ describe("createCodexDynamicToolBridge", () => {
372372
expect(badExecute).not.toHaveBeenCalled();
373373
});
374374

375+
it("quarantines unreadable dynamic tool descriptors without dropping healthy siblings", () => {
376+
const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
377+
const poisonedName = createTool({
378+
name: "fuzzplugin_unreadable_name",
379+
execute: vi.fn(),
380+
});
381+
Object.defineProperty(poisonedName, "name", {
382+
enumerable: true,
383+
get() {
384+
throw new Error("fuzzplugin dynamic tool name getter exploded");
385+
},
386+
});
387+
const poisonedSchema = createTool({
388+
name: "fuzzplugin_unreadable_schema",
389+
execute: vi.fn(),
390+
});
391+
Object.defineProperty(poisonedSchema, "parameters", {
392+
enumerable: true,
393+
get() {
394+
throw new Error("fuzzplugin dynamic tool schema getter exploded");
395+
},
396+
});
397+
const invalidName = createTool({
398+
name: "",
399+
execute: vi.fn(),
400+
});
401+
const poisonedExecute = createTool({
402+
name: "fuzzplugin_unreadable_execute",
403+
});
404+
Object.defineProperty(poisonedExecute, "execute", {
405+
enumerable: true,
406+
get() {
407+
throw new Error("fuzzplugin dynamic tool execute getter exploded");
408+
},
409+
});
410+
411+
const bridge = createCodexDynamicToolBridge({
412+
tools: [
413+
poisonedName,
414+
poisonedSchema,
415+
invalidName,
416+
poisonedExecute,
417+
createTool({ name: "message" }),
418+
],
419+
signal: new AbortController().signal,
420+
});
421+
422+
expect(bridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
423+
expect(bridge.specs.map((tool) => tool.name)).toEqual(["message"]);
424+
expect(bridge.telemetry.quarantinedTools).toEqual([
425+
{
426+
tool: "tool[0]",
427+
violations: ["tool[0].name is unreadable"],
428+
},
429+
{
430+
tool: "fuzzplugin_unreadable_schema",
431+
violations: ["fuzzplugin_unreadable_schema.inputSchema is unreadable"],
432+
},
433+
{
434+
tool: "tool[2]",
435+
violations: ["tool[2].name must be a non-empty string"],
436+
},
437+
{
438+
tool: "fuzzplugin_unreadable_execute",
439+
violations: [
440+
"fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks",
441+
],
442+
},
443+
]);
444+
expect(warn).toHaveBeenCalledWith(
445+
expect.stringContaining(
446+
"tool[0], fuzzplugin_unreadable_schema, tool[2], fuzzplugin_unreadable_execute",
447+
),
448+
expect.objectContaining({
449+
tools: [
450+
{
451+
tool: "tool[0]",
452+
violations: ["tool[0].name is unreadable"],
453+
},
454+
{
455+
tool: "fuzzplugin_unreadable_schema",
456+
violations: ["fuzzplugin_unreadable_schema.inputSchema is unreadable"],
457+
},
458+
{
459+
tool: "tool[2]",
460+
violations: ["tool[2].name must be a non-empty string"],
461+
},
462+
{
463+
tool: "fuzzplugin_unreadable_execute",
464+
violations: [
465+
"fuzzplugin_unreadable_execute could not be wrapped for before-tool-call hooks",
466+
],
467+
},
468+
],
469+
}),
470+
);
471+
472+
const registeredBridge = createCodexDynamicToolBridge({
473+
tools: [poisonedExecute, createTool({ name: "message" })],
474+
registeredTools: [
475+
createTool({ name: "fuzzplugin_unreadable_execute" }),
476+
createTool({ name: "message" }),
477+
],
478+
signal: new AbortController().signal,
479+
});
480+
481+
expect(registeredBridge.availableSpecs.map((tool) => tool.name)).toEqual(["message"]);
482+
expect(registeredBridge.specs.map((tool) => tool.name)).toEqual(["message"]);
483+
});
484+
375485
it("can expose all dynamic tools directly for compatibility", () => {
376486
const bridge = createCodexDynamicToolBridge({
377487
tools: [createTool({ name: "web_search" }), createTool({ name: "message" })],

0 commit comments

Comments
 (0)