Skip to content

Commit d2a789e

Browse files
committed
test(exec): cover director allowlist bypass through tool_search and promote
1 parent 43e7eef commit d2a789e

3 files changed

Lines changed: 134 additions & 0 deletions

File tree

‎src/agent/fleet-verbs-mount.test.ts‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,48 @@ describe("primary fleet verb mount", () => {
5555
await toolset.dispose();
5656
});
5757

58+
test("createAgentToolset mounts tool_search by default", async () => {
59+
const cwd = mkdtempSync(join(tmpdir(), "corbits-fleet-mount-"));
60+
const { createAgentToolset } = await import("./tools.js");
61+
const permissionGate = {
62+
check: async () => ({ allowed: true }),
63+
getSkipPermissions: () => false,
64+
} as never;
65+
66+
const toolset = await createAgentToolset({ cwd, permissionGate });
67+
try {
68+
const names = toolset.dynamicRunner
69+
.currentDefinitions()
70+
.map((d) => d.name);
71+
expect(names).toContain("tool_search");
72+
} finally {
73+
await toolset.dispose();
74+
}
75+
});
76+
77+
test("createAgentToolset omits tool_search when the allow list excludes it", async () => {
78+
const cwd = mkdtempSync(join(tmpdir(), "corbits-fleet-mount-"));
79+
const { createAgentToolset } = await import("./tools.js");
80+
const permissionGate = {
81+
check: async () => ({ allowed: true }),
82+
getSkipPermissions: () => false,
83+
} as never;
84+
85+
const toolset = await createAgentToolset({
86+
cwd,
87+
permissionGate,
88+
toolSearchAllow: ["read_file"],
89+
});
90+
try {
91+
const names = toolset.dynamicRunner
92+
.currentDefinitions()
93+
.map((d) => d.name);
94+
expect(names).not.toContain("tool_search");
95+
} finally {
96+
await toolset.dispose();
97+
}
98+
});
99+
58100
test("createAgentToolset mounts wait_agents when mountWaitAgents is true (exec primary)", async () => {
59101
const cwd = mkdtempSync(join(tmpdir(), "corbits-fleet-mount-"));
60102
const { createAgentToolset } = await import("./tools.js");

‎src/agent/tool-search.test.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ describe("createToolIndex", () => {
227227
test("returns nothing for an empty query", () => {
228228
expect(index.search(" ")).toEqual([]);
229229
});
230+
231+
test("with an allow list, never returns tools outside the allow list", () => {
232+
const allowed = createToolIndex(() => defs, [], ["present"]);
233+
expect(allowed.search("pages")).toContain("present");
234+
expect(allowed.search("linear")).not.toContain("mcp__linear__create_issue");
235+
});
230236
});
231237

232238
function call(

‎src/exec/runner.test.ts‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { DIRECTOR_REGISTRY } from "../agent/directors/registry.js";
3+
import { createAdvertisedToolset } from "../session/assemble-runtime.js";
4+
import {
5+
createExecToolCallGate,
6+
createExecToolPromoter,
7+
isExecOverlayToolAllowed,
8+
resolveExecDirectorOverlay,
9+
resolveExecDirectorOverlayForPackage,
10+
} from "./runner.js";
11+
12+
const OUTSIDE_ALLOW = "mcp__linear__create_issue";
13+
14+
describe("exec director allowlist", () => {
15+
test("explorer overlay narrows advertised tools to the package allow list", () => {
16+
const overlay = resolveExecDirectorOverlay("explorer");
17+
expect(overlay.advertisedAllow).toBeDefined();
18+
expect(overlay.advertisedAllow).toContain("read_file");
19+
expect(overlay.advertisedAllow).toContain("run_shell");
20+
expect(overlay.advertisedAllow).not.toContain("tool_search");
21+
expect(overlay.advertisedAllow).not.toContain(OUTSIDE_ALLOW);
22+
});
23+
24+
test("critic overlay narrows advertised tools to the package allow list", () => {
25+
const overlay = resolveExecDirectorOverlay("critic");
26+
expect(overlay.advertisedAllow).toBeDefined();
27+
expect(overlay.advertisedAllow).not.toContain("tool_search");
28+
expect(overlay.advertisedAllow).not.toContain(OUTSIDE_ALLOW);
29+
});
30+
31+
test("skywalker keeps the product default — no allow list", () => {
32+
const overlay = resolveExecDirectorOverlay("skywalker");
33+
expect(overlay.advertisedAllow).toBeUndefined();
34+
expect(overlay.mountFleet).toBe(true);
35+
});
36+
37+
test("deny entries are subtracted from the allow list", () => {
38+
const pkg = {
39+
...DIRECTOR_REGISTRY.explorer,
40+
tools: { allow: ["read_file", "run_shell"], deny: ["run_shell"] },
41+
};
42+
expect(resolveExecDirectorOverlayForPackage(pkg).advertisedAllow).toEqual([
43+
"read_file",
44+
]);
45+
});
46+
47+
test("a deny-only package config is rejected loudly", () => {
48+
const pkg = {
49+
...DIRECTOR_REGISTRY.explorer,
50+
tools: { deny: ["run_shell"] },
51+
};
52+
expect(() => resolveExecDirectorOverlayForPackage(pkg)).toThrow(/deny/);
53+
});
54+
55+
test("promote cannot make an outside-allow tool callable under explorer", () => {
56+
const overlay = resolveExecDirectorOverlay("explorer");
57+
expect(isExecOverlayToolAllowed(overlay, OUTSIDE_ALLOW)).toBe(false);
58+
const { activated, computeAdvertised, isAdvertised } =
59+
createAdvertisedToolset({
60+
sessionMode: "orchestrator",
61+
toolAvailability: { languageServerAvailable: true },
62+
getProvider: () => ({ providerName: "test", model: "test-model" }),
63+
builtInPrefix: overlay.advertisedAllow,
64+
});
65+
const promote = createExecToolPromoter({
66+
activate: (names) =>
67+
activated.activate(
68+
names.filter((name) => isExecOverlayToolAllowed(overlay, name)),
69+
),
70+
currentDefinitions: () => [],
71+
computeAdvertised,
72+
updateDirectorTools: () => undefined,
73+
});
74+
promote([OUTSIDE_ALLOW]);
75+
expect(activated.has(OUTSIDE_ALLOW)).toBe(false);
76+
expect(isAdvertised(OUTSIDE_ALLOW)).toBe(false);
77+
expect(
78+
createExecToolCallGate(isAdvertised, { isCodex: false })(OUTSIDE_ALLOW),
79+
).toBe(false);
80+
});
81+
82+
test("skywalker overlay leaves every tool allowed", () => {
83+
const overlay = resolveExecDirectorOverlay("skywalker");
84+
expect(isExecOverlayToolAllowed(overlay, OUTSIDE_ALLOW)).toBe(true);
85+
});
86+
});

0 commit comments

Comments
 (0)