diff --git a/src/agents/pi-tools.policy.ts b/src/agents/pi-tools.policy.ts index 1879a6218..d6e125e33 100644 --- a/src/agents/pi-tools.policy.ts +++ b/src/agents/pi-tools.policy.ts @@ -103,7 +103,11 @@ type ToolPolicyConfig = { function unionAllow(base?: string[], extra?: string[]) { if (!Array.isArray(extra) || extra.length === 0) return base; - if (!Array.isArray(base) || base.length === 0) return base; + // If the user is using alsoAllow without an allowlist, treat it as additive on top of + // an implicit allow-all policy. + if (!Array.isArray(base) || base.length === 0) { + return Array.from(new Set(["*", ...extra])); + } return Array.from(new Set([...base, ...extra])); } @@ -111,7 +115,9 @@ function pickToolPolicy(config?: ToolPolicyConfig): SandboxToolPolicy | undefine if (!config) return undefined; const allow = Array.isArray(config.allow) ? unionAllow(config.allow, config.alsoAllow) - : undefined; + : Array.isArray(config.alsoAllow) && config.alsoAllow.length > 0 + ? unionAllow(undefined, config.alsoAllow) + : undefined; const deny = Array.isArray(config.deny) ? config.deny : undefined; if (!allow && !deny) return undefined; return { allow, deny }; diff --git a/src/gateway/tools-invoke-http.test.ts b/src/gateway/tools-invoke-http.test.ts index 956ac51dd..f08035885 100644 --- a/src/gateway/tools-invoke-http.test.ts +++ b/src/gateway/tools-invoke-http.test.ts @@ -83,6 +83,34 @@ describe("POST /tools/invoke", () => { await server.close(); }); + it("supports tools.alsoAllow without allow/profile (implicit allow-all)", async () => { + testState.agentsConfig = { + list: [{ id: "main" }], + } as any; + + await fs.mkdir(path.dirname(CONFIG_PATH_CLAWDBOT), { recursive: true }); + await fs.writeFile( + CONFIG_PATH_CLAWDBOT, + JSON.stringify({ tools: { alsoAllow: ["sessions_list"] } }, null, 2), + "utf-8", + ); + + const port = await getFreePort(); + const server = await startGatewayServer(port, { bind: "loopback" }); + + const res = await fetch(`http://127.0.0.1:${port}/tools/invoke`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ tool: "sessions_list", action: "json", args: {}, sessionKey: "main" }), + }); + + expect(res.status).toBe(200); + const body = await res.json(); + expect(body.ok).toBe(true); + + await server.close(); + }); + it("accepts password auth when bearer token matches", async () => { testState.agentsConfig = { list: [