Skip to content

Commit 79dfc3e

Browse files
fix(clawsweeper): address review for automerge-openclaw-openclaw-84699 (1)
1 parent 5afcf67 commit 79dfc3e

2 files changed

Lines changed: 72 additions & 21 deletions

File tree

src/commands/doctor/shared/plugin-tool-allowlist-warnings.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,32 @@ describe("collectPluginToolAllowlistWarnings", () => {
115115
]);
116116
});
117117

118+
it("warns when sandbox allowlist covers only one configured MCP server", () => {
119+
const warnings = collectPluginToolAllowlistWarnings({
120+
cfg: {
121+
agents: { defaults: { sandbox: { mode: "all" } } },
122+
mcp: {
123+
servers: {
124+
gmail: { command: "node", args: ["gmail-server.js"] },
125+
outlook: { command: "node", args: ["outlook-server.js"] },
126+
},
127+
},
128+
tools: {
129+
sandbox: {
130+
tools: {
131+
alsoAllow: ["outlook__*"],
132+
},
133+
},
134+
},
135+
},
136+
manifestRegistry,
137+
});
138+
139+
expect(warnings).toEqual([
140+
'- mcp.servers defines 2 MCP servers ("gmail", "outlook"), but tools.sandbox.tools.alsoAllow does not include "bundle-mcp", "group:plugins", or a matching server-prefixed MCP tool name/glob such as "<server>__*". Sandboxed agents will filter bundled MCP tools before provider requests. Add "bundle-mcp" to tools.sandbox.tools.alsoAllow (or use "group:plugins" / server globs) if those MCP tools should be visible; use tools.sandbox.tools.allow: [] only when you intentionally want no sandbox allow gate.',
141+
]);
142+
});
143+
118144
it("uses a config-path source label when sandbox allowlist is unset", () => {
119145
const warnings = collectPluginToolAllowlistWarnings({
120146
cfg: {

src/commands/doctor/shared/plugin-tool-allowlist-warnings.ts

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,10 @@ function buildMcpToolNamePrefixes(serverNames: readonly string[]): string[] {
389389
.filter(Boolean);
390390
}
391391

392-
function entriesMatchAnyMcpTool(
392+
function entriesMatchMcpTool(
393393
entries: readonly string[],
394394
serverNames: readonly string[],
395+
mode: "any" | "every",
395396
): boolean {
396397
const normalizedEntries = entries.map(normalizeToolName).filter(Boolean);
397398
if (
@@ -402,23 +403,43 @@ function entriesMatchAnyMcpTool(
402403
return true;
403404
}
404405
const serverPrefixes = buildMcpToolNamePrefixes(serverNames);
405-
if (
406-
normalizedEntries.some((entry) =>
407-
serverPrefixes.some((prefix) => entry.length > prefix.length && entry.startsWith(prefix)),
408-
)
409-
) {
410-
return true;
411-
}
412406
const patterns = compileGlobPatterns({ raw: normalizedEntries, normalize: normalizeToolName });
413-
if (patterns.length === 0) {
414-
return false;
407+
const probeNames = buildMcpProbeToolNames(serverNames).map(normalizeToolName);
408+
const prefixOrPatternMatches = (prefix: string, index: number) =>
409+
normalizedEntries.some((entry) => entry.length > prefix.length && entry.startsWith(prefix)) ||
410+
matchesAnyGlobPattern(probeNames[index] ?? "", patterns);
411+
return mode === "every"
412+
? serverPrefixes.every((prefix, index) => prefixOrPatternMatches(prefix, index))
413+
: serverPrefixes.some((prefix, index) => prefixOrPatternMatches(prefix, index));
414+
}
415+
416+
function entriesMatchAnyMcpTool(
417+
entries: readonly string[],
418+
serverNames: readonly string[],
419+
): boolean {
420+
return entriesMatchMcpTool(entries, serverNames, "any");
421+
}
422+
423+
function entriesMatchEveryMcpTool(
424+
entries: readonly string[],
425+
serverNames: readonly string[],
426+
): boolean {
427+
return entriesMatchMcpTool(entries, serverNames, "every");
428+
}
429+
430+
function sandboxPolicyAllowsAllMcpServers(
431+
policy: unknown,
432+
serverNames: readonly string[],
433+
): boolean {
434+
const allow = getList(policy, "allow");
435+
if (Array.isArray(allow) && allow.length === 0) {
436+
return true;
415437
}
416-
return buildMcpProbeToolNames(serverNames).some((probeName) =>
417-
matchesAnyGlobPattern(normalizeToolName(probeName), patterns),
418-
);
438+
const entries = [...(allow ?? []), ...(getList(policy, "alsoAllow") ?? [])];
439+
return entriesMatchEveryMcpTool(entries, serverNames);
419440
}
420441

421-
function sandboxAllowGateAllowsMcp(policy: unknown, serverNames: readonly string[]): boolean {
442+
function toolPolicyAllowsAnyMcpServer(policy: unknown, serverNames: readonly string[]): boolean {
422443
const allow = getList(policy, "allow");
423444
if (Array.isArray(allow) && allow.length === 0) {
424445
return true;
@@ -427,16 +448,20 @@ function sandboxAllowGateAllowsMcp(policy: unknown, serverNames: readonly string
427448
return entriesMatchAnyMcpTool(entries, serverNames);
428449
}
429450

430-
function sandboxPolicyIntentionallyDeniesMcp(
451+
function toolPolicyDeniesAllMcpServers(policy: unknown, serverNames: readonly string[]): boolean {
452+
const deny = getList(policy, "deny") ?? [];
453+
return entriesMatchEveryMcpTool(deny, serverNames);
454+
}
455+
456+
function sandboxPolicyIntentionallyDeniesAllMcpServers(
431457
policy: unknown,
432458
serverNames: readonly string[],
433459
): boolean {
434-
const deny = getList(policy, "deny") ?? [];
435-
return entriesMatchAnyMcpTool(deny, serverNames);
460+
return toolPolicyDeniesAllMcpServers(policy, serverNames);
436461
}
437462

438463
function nonSandboxToolPolicyBlocksMcp(policy: unknown, serverNames: readonly string[]): boolean {
439-
if (sandboxPolicyIntentionallyDeniesMcp(policy, serverNames)) {
464+
if (toolPolicyDeniesAllMcpServers(policy, serverNames)) {
440465
return true;
441466
}
442467
const allow = getList(policy, "allow");
@@ -453,7 +478,7 @@ function profileToolPolicyBlocksMcp(policy: unknown, serverNames: readonly strin
453478
resolveToolProfilePolicy(profile),
454479
getList(policy, "alsoAllow"),
455480
);
456-
return Boolean(profilePolicy && !sandboxAllowGateAllowsMcp(profilePolicy, serverNames));
481+
return Boolean(profilePolicy && !toolPolicyAllowsAnyMcpServer(profilePolicy, serverNames));
457482
}
458483

459484
function nonSandboxToolPoliciesBlockMcp(params: {
@@ -515,8 +540,8 @@ function collectSandboxMcpAllowlistWarnings(cfg: OpenClawConfig): string[] {
515540
const issueSources = sandboxPolicies
516541
.filter(
517542
({ policy }) =>
518-
!sandboxAllowGateAllowsMcp(policy, serverNames) &&
519-
!sandboxPolicyIntentionallyDeniesMcp(policy, serverNames),
543+
!sandboxPolicyAllowsAllMcpServers(policy, serverNames) &&
544+
!sandboxPolicyIntentionallyDeniesAllMcpServers(policy, serverNames),
520545
)
521546
.filter(({ nonSandboxToolPolicyBlocksMcp }) => !nonSandboxToolPolicyBlocksMcp)
522547
.flatMap(({ labels }) => labels);

0 commit comments

Comments
 (0)