fix: harden exec sandbox fallback semantics (#23398) (thanks @bmendonca3)

This commit is contained in:
Peter Steinberger
2026-02-22 10:49:15 +01:00
parent c76a47cce2
commit 1b327da6e3
8 changed files with 49 additions and 7 deletions

View File

@@ -127,4 +127,31 @@ describe("exec host env validation", () => {
}),
).rejects.toThrow(/Security Violation: Environment variable 'LD_DEBUG' is forbidden/);
});
it("defaults to gateway when sandbox runtime is unavailable", async () => {
const { createExecTool } = await import("./bash-tools.exec.js");
const tool = createExecTool({ security: "full", ask: "off" });
const err = await tool
.execute("call1", {
command: "echo ok",
host: "sandbox",
})
.then(() => null)
.catch((error: unknown) => (error instanceof Error ? error : new Error(String(error))));
expect(err).toBeTruthy();
expect(err?.message).toMatch(/exec host not allowed/);
expect(err?.message).toMatch(/tools\.exec\.host=gateway/);
});
it("fails closed when sandbox host is explicitly configured without sandbox runtime", async () => {
const { createExecTool } = await import("./bash-tools.exec.js");
const tool = createExecTool({ host: "sandbox", security: "full", ask: "off" });
await expect(
tool.execute("call1", {
command: "echo ok",
}),
).rejects.toThrow(/sandbox runtime is unavailable/);
});
});

View File

@@ -33,7 +33,12 @@ test("exec disposes PTY listeners after normal exit", async () => {
kill: vi.fn(),
}));
const tool = createExecTool({ allowBackground: false });
const tool = createExecTool({
allowBackground: false,
host: "gateway",
security: "full",
ask: "off",
});
const result = await tool.execute("toolcall", {
command: "echo ok",
pty: true,
@@ -64,7 +69,12 @@ test("exec tears down PTY resources on timeout", async () => {
kill,
}));
const tool = createExecTool({ allowBackground: false });
const tool = createExecTool({
allowBackground: false,
host: "gateway",
security: "full",
ask: "off",
});
await expect(
tool.execute("toolcall", {
command: "sleep 5",

View File

@@ -26,7 +26,12 @@ test("exec cleans session state when PTY fallback spawn also fails", async () =>
.mockRejectedValueOnce(new Error("pty spawn failed"))
.mockRejectedValueOnce(new Error("child fallback failed"));
const tool = createExecTool({ allowBackground: false });
const tool = createExecTool({
allowBackground: false,
host: "gateway",
security: "full",
ask: "off",
});
await expect(
tool.execute("toolcall", {

View File

@@ -279,7 +279,7 @@ export function createExecTool(
if (elevatedRequested) {
logInfo(`exec: elevated command ${truncateMiddle(params.command, 120)}`);
}
const configuredHost = defaults?.host ?? "sandbox";
const configuredHost = defaults?.host ?? (defaults?.sandbox ? "sandbox" : "gateway");
const sandboxHostConfigured = defaults?.host === "sandbox";
const requestedHost = normalizeExecHost(params.host) ?? null;
let host: ExecHost = requestedHost ?? configuredHost;

View File

@@ -602,7 +602,6 @@ describe("Agent-specific tool filtering", () => {
tools: {
deny: ["process"],
exec: {
host: "gateway",
security: "full",
ask: "off",
},