Skip to content

Commit c01b172

Browse files
committed
fix: preserve RPC owner tool access
1 parent de55537 commit c01b172

2 files changed

Lines changed: 43 additions & 1 deletion

File tree

src/gateway/server-methods/tools-invoke.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function resolveRpcErrorCode(params: {
3434

3535
/** Handles `tools.invoke` with protocol-shaped success and failure payloads. */
3636
export const toolsInvokeHandlers: GatewayRequestHandlers = {
37-
"tools.invoke": async ({ params, respond, context }) => {
37+
"tools.invoke": async ({ params, respond, context, client }) => {
3838
if (!validateToolsInvokeParams(params)) {
3939
respond(
4040
false,
@@ -59,6 +59,7 @@ export const toolsInvokeHandlers: GatewayRequestHandlers = {
5959
const outcome = await invokeGatewayTool({
6060
cfg: context.getRuntimeConfig(),
6161
input: params,
62+
senderIsOwner: client?.connect?.scopes?.includes("operator.admin"),
6263
toolCallIdPrefix: "rpc",
6364
approvalMode: params.confirm === true ? "request" : "report",
6465
});

src/gateway/tools-invoke-http.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,47 @@ describe("tools.invoke Gateway RPC", () => {
10681068
expect(hookCtx.sessionKey).toBe("agent:main:main");
10691069
});
10701070

1071+
it("keeps owner-only tools unavailable to non-owner RPC callers despite gateway.tools.allow", async () => {
1072+
setMainAllowedTools({
1073+
allow: ["cron", "gateway", "nodes"],
1074+
gatewayAllow: ["cron", "gateway", "nodes"],
1075+
});
1076+
1077+
for (const tool of ["cron", "gateway", "nodes"]) {
1078+
const call = await invokeToolsRpc({
1079+
name: tool,
1080+
args: {},
1081+
sessionKey: "main",
1082+
});
1083+
1084+
expect(call?.[0], tool).toBe(true);
1085+
expect(call?.[1]?.ok, tool).toBe(false);
1086+
expect(call?.[1]?.toolName, tool).toBe(tool);
1087+
const error = call?.[1]?.error as { code?: string; message?: string } | undefined;
1088+
expect(error?.code, tool).toBe("not_found");
1089+
}
1090+
expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(false);
1091+
});
1092+
1093+
it("keeps operator.admin RPC callers as owner for explicitly allowed owner-only tools", async () => {
1094+
setMainAllowedTools({ allow: ["nodes"], gatewayAllow: ["nodes"] });
1095+
1096+
const call = await invokeToolsRpc(
1097+
{
1098+
name: "nodes",
1099+
args: {},
1100+
sessionKey: "main",
1101+
},
1102+
["operator.admin"],
1103+
);
1104+
1105+
expect(call?.[0]).toBe(true);
1106+
expect(call?.[1]?.ok).toBe(true);
1107+
expect(call?.[1]?.toolName).toBe("nodes");
1108+
expect(call?.[1]?.output).toEqual({ ok: true, result: "nodes" });
1109+
expect(lastCreateOpenClawToolsContext?.senderIsOwner).toBe(true);
1110+
});
1111+
10711112
it("returns typed approval-needed refusal when the policy hook blocks", async () => {
10721113
setMainAllowedTools({ allow: ["tools_invoke_test"] });
10731114
hookMocks.runBeforeToolCallHook.mockResolvedValueOnce({

0 commit comments

Comments
 (0)