Merge f0d03344658377a427b56888df623675cda26342 into 598f1826d8b2bc969aace2c6459824737667218c

This commit is contained in:
Jerry-Xin 2026-03-21 12:03:55 +08:00 committed by GitHub
commit 2138bcc4a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 88 additions and 4 deletions

View File

@ -271,4 +271,76 @@ describe("resolveEffectiveToolPolicy", () => {
const result = resolveEffectiveToolPolicy({ config: cfg, agentId: "coder" });
expect(result.profileAlsoAllow).toEqual(["read", "write", "edit"]);
});
it("does not implicitly expose global exec when agent has explicit alsoAllow without it", () => {
// Regression test for #47487: tool profile restrictions not enforced
const cfg = {
tools: {
exec: { security: "full", ask: "off" },
},
agents: {
list: [
{
id: "messenger",
tools: {
profile: "messaging",
alsoAllow: ["web_search"],
},
},
],
},
} as OpenClawConfig;
const result = resolveEffectiveToolPolicy({ config: cfg, agentId: "messenger" });
expect(result.profileAlsoAllow).toEqual(["web_search"]);
expect(result.profileAlsoAllow).not.toContain("exec");
expect(result.profileAlsoAllow).not.toContain("process");
});
it("does not implicitly expose global fs when agent has explicit empty alsoAllow", () => {
const cfg = {
tools: {
fs: { workspaceOnly: false },
},
agents: {
list: [
{
id: "restricted",
tools: {
profile: "messaging",
alsoAllow: [],
},
},
],
},
} as OpenClawConfig;
const result = resolveEffectiveToolPolicy({ config: cfg, agentId: "restricted" });
// Empty array is returned (not undefined) when explicit alsoAllow is set
expect(result.profileAlsoAllow).toEqual([]);
expect(result.profileAlsoAllow).not.toContain("read");
expect(result.profileAlsoAllow).not.toContain("write");
expect(result.profileAlsoAllow).not.toContain("edit");
});
it("still uses agent-level exec section for implicit exposure even with explicit alsoAllow", () => {
const cfg = {
tools: {
profile: "messaging",
},
agents: {
list: [
{
id: "coder",
tools: {
alsoAllow: ["web_search"],
exec: { host: "sandbox" },
},
},
],
},
} as OpenClawConfig;
const result = resolveEffectiveToolPolicy({ config: cfg, agentId: "coder" });
expect(result.profileAlsoAllow).toContain("web_search");
expect(result.profileAlsoAllow).toContain("exec");
expect(result.profileAlsoAllow).toContain("process");
});
});

View File

@ -210,18 +210,25 @@ function hasExplicitToolSection(section: unknown): boolean {
function resolveImplicitProfileAlsoAllow(params: {
globalTools?: OpenClawConfig["tools"];
agentTools?: AgentToolsConfig;
/** When agent has explicit alsoAllow, skip global tool sections for implicit exposure. */
agentHasExplicitAlsoAllow?: boolean;
}): string[] | undefined {
const implicit = new Set<string>();
// When agent has explicit alsoAllow set, only agent-level tool sections should
// trigger implicit exposure. Global tool sections should not override agent's
// explicit restriction. This prevents tools.exec at global level from bypassing
// an agent's profile + alsoAllow restriction.
const useGlobalSections = !params.agentHasExplicitAlsoAllow;
if (
hasExplicitToolSection(params.agentTools?.exec) ||
hasExplicitToolSection(params.globalTools?.exec)
(useGlobalSections && hasExplicitToolSection(params.globalTools?.exec))
) {
implicit.add("exec");
implicit.add("process");
}
if (
hasExplicitToolSection(params.agentTools?.fs) ||
hasExplicitToolSection(params.globalTools?.fs)
(useGlobalSections && hasExplicitToolSection(params.globalTools?.fs))
) {
implicit.add("read");
implicit.add("write");
@ -260,9 +267,14 @@ export function resolveEffectiveToolPolicy(params: {
modelProvider: params.modelProvider,
modelId: params.modelId,
});
const agentExplicitAlsoAllow = resolveExplicitProfileAlsoAllow(agentTools);
const explicitProfileAlsoAllow =
resolveExplicitProfileAlsoAllow(agentTools) ?? resolveExplicitProfileAlsoAllow(globalTools);
const implicitProfileAlsoAllow = resolveImplicitProfileAlsoAllow({ globalTools, agentTools });
agentExplicitAlsoAllow ?? resolveExplicitProfileAlsoAllow(globalTools);
const implicitProfileAlsoAllow = resolveImplicitProfileAlsoAllow({
globalTools,
agentTools,
agentHasExplicitAlsoAllow: agentExplicitAlsoAllow !== undefined,
});
const profileAlsoAllow =
explicitProfileAlsoAllow || implicitProfileAlsoAllow
? Array.from(