mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 17:27:28 +00:00
fix(agents): add nodes to owner-only tool policy fallbacks
The nodes tool was missing from OWNER_ONLY_TOOL_NAME_FALLBACKS in tool-policy.ts. applyOwnerOnlyToolPolicy() correctly removed gateway and cron for non-owners but kept nodes, which internally issues privileged gateway calls: node.pair.approve (operator.pairing) and node.invoke (operator.write). A non-owner sender could approve pending node pairings and invoke arbitrary node commands, extending to system.run on paired nodes. Add nodes to the fallback owner-only set. Non-owners no longer receive the nodes tool after policy application; owners retain it. Fixes GHSA-r26r-9hxr-r792
This commit is contained in:
@@ -98,6 +98,8 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/context pruning: prune image-only tool results during soft-trim, align context-pruning coverage with the new tool-result contract, and extend historical image cleanup to the same screenshot-heavy session path. (#43045) Thanks @MoerAI.
|
||||
- fix(models): guard optional model.input capability checks (#42096) thanks @andyliu
|
||||
- Security/plugin runtime: stop unauthenticated plugin HTTP routes from inheriting synthetic admin gateway scopes when they call `runtime.subagent.*`, so admin-only methods like `sessions.delete` stay blocked without gateway auth.
|
||||
- Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via `session_status`.
|
||||
- Security/nodes: treat the `nodes` agent tool as owner-only fallback policy so non-owner senders cannot reach paired-node approval or invoke paths through the shared tool set.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ describe("tool-policy", () => {
|
||||
expect(isOwnerOnlyToolName("whatsapp_login")).toBe(true);
|
||||
expect(isOwnerOnlyToolName("cron")).toBe(true);
|
||||
expect(isOwnerOnlyToolName("gateway")).toBe(true);
|
||||
expect(isOwnerOnlyToolName("nodes")).toBe(true);
|
||||
expect(isOwnerOnlyToolName("read")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -107,6 +108,27 @@ describe("tool-policy", () => {
|
||||
expect(applyOwnerOnlyToolPolicy(tools, false)).toEqual([]);
|
||||
expect(applyOwnerOnlyToolPolicy(tools, true)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("strips nodes for non-owner senders via fallback policy", () => {
|
||||
const tools = [
|
||||
{
|
||||
name: "read",
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
execute: async () => ({ content: [], details: {} }) as any,
|
||||
},
|
||||
{
|
||||
name: "nodes",
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
execute: async () => ({ content: [], details: {} }) as any,
|
||||
},
|
||||
] as unknown as AnyAgentTool[];
|
||||
|
||||
expect(applyOwnerOnlyToolPolicy(tools, false).map((tool) => tool.name)).toEqual(["read"]);
|
||||
expect(applyOwnerOnlyToolPolicy(tools, true).map((tool) => tool.name)).toEqual([
|
||||
"read",
|
||||
"nodes",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("TOOL_POLICY_CONFORMANCE", () => {
|
||||
|
||||
@@ -28,7 +28,12 @@ function wrapOwnerOnlyToolExecution(tool: AnyAgentTool, senderIsOwner: boolean):
|
||||
};
|
||||
}
|
||||
|
||||
const OWNER_ONLY_TOOL_NAME_FALLBACKS = new Set<string>(["whatsapp_login", "cron", "gateway"]);
|
||||
const OWNER_ONLY_TOOL_NAME_FALLBACKS = new Set<string>([
|
||||
"whatsapp_login",
|
||||
"cron",
|
||||
"gateway",
|
||||
"nodes",
|
||||
]);
|
||||
|
||||
export function isOwnerOnlyToolName(name: string) {
|
||||
return OWNER_ONLY_TOOL_NAME_FALLBACKS.has(normalizeToolName(name));
|
||||
|
||||
Reference in New Issue
Block a user