fix: preserve explicit node routing under elevated auto exec

This commit is contained in:
Ayaan Zaidi 2026-04-06 13:09:00 +05:30
parent 7bae391f33
commit c0a0e295cb
3 changed files with 63 additions and 19 deletions

View File

@ -221,6 +221,22 @@ describe("resolveExecTarget", () => {
});
});
it("keeps explicit node override under elevated requests when configured target is auto", () => {
expect(
resolveExecTarget({
configuredTarget: "auto",
requestedTarget: "node",
elevatedRequested: true,
sandboxAvailable: false,
}),
).toMatchObject({
configuredTarget: "auto",
requestedTarget: "node",
selectedTarget: "node",
effectiveHost: "node",
});
});
it("honours node target for elevated requests when configured target is node", () => {
expect(
resolveExecTarget({
@ -252,20 +268,17 @@ describe("resolveExecTarget", () => {
});
});
it("silently discards mismatched requestedTarget under elevated+node", () => {
expect(
it("rejects mismatched requestedTarget under elevated+node", () => {
expect(() =>
resolveExecTarget({
configuredTarget: "node",
requestedTarget: "gateway",
elevatedRequested: true,
sandboxAvailable: false,
}),
).toMatchObject({
configuredTarget: "node",
requestedTarget: "gateway",
selectedTarget: "node",
effectiveHost: "node",
});
).toThrow(
"exec host not allowed (requested gateway; configured host is node; set tools.exec.host=gateway or auto to allow this override).",
);
});
});

View File

@ -242,15 +242,6 @@ export function resolveExecTarget(params: {
}) {
const configuredTarget = params.configuredTarget ?? "auto";
const requestedTarget = params.requestedTarget ?? null;
if (params.elevatedRequested) {
const elevatedTarget = configuredTarget === "node" ? ("node" as const) : ("gateway" as const);
return {
configuredTarget,
requestedTarget,
selectedTarget: elevatedTarget,
effectiveHost: elevatedTarget,
};
}
if (
requestedTarget &&
!isRequestedExecTargetAllowed({
@ -273,12 +264,17 @@ export function resolveExecTarget(params: {
);
}
const selectedTarget = requestedTarget ?? configuredTarget;
const resolvedTarget = params.elevatedRequested
? selectedTarget === "node"
? "node"
: "gateway"
: selectedTarget;
const effectiveHost =
selectedTarget === "auto" ? (params.sandboxAvailable ? "sandbox" : "gateway") : selectedTarget;
resolvedTarget === "auto" ? (params.sandboxAvailable ? "sandbox" : "gateway") : resolvedTarget;
return {
configuredTarget,
requestedTarget,
selectedTarget,
selectedTarget: resolvedTarget,
effectiveHost,
};
}

View File

@ -458,6 +458,41 @@ describe("exec approvals", () => {
expect(prepareCwd).toBeUndefined();
});
it("routes explicit host=node to node invoke when elevated default is on under auto host", async () => {
const calls: string[] = [];
vi.mocked(callGatewayTool).mockImplementation(async (method, _opts, params) => {
calls.push(method);
if (method === "node.invoke") {
const invoke = params as { command?: string };
if (invoke.command === "system.run.prepare") {
return buildPreparedSystemRunPayload(params);
}
if (invoke.command === "system.run") {
return { payload: { success: true, stdout: "node-ok" } };
}
}
return { ok: true };
});
const tool = createExecTool({
host: "auto",
ask: "off",
security: "full",
approvalRunningNoticeMs: 0,
elevated: { enabled: true, allowed: true, defaultLevel: "on" },
});
const result = await tool.execute("call-auto-node-elevated-default", {
command: "echo gateway-ok",
host: "node",
});
expect(result.details.status).toBe("completed");
expect(getResultText(result)).toContain("node-ok");
expect(calls).toContain("node.invoke");
});
it("honors ask=off for elevated gateway exec without prompting", async () => {
const calls: string[] = [];
vi.mocked(callGatewayTool).mockImplementation(async (method) => {