Exec approvals: share policy scope collection

This commit is contained in:
Gustavo Madeira Santana
2026-04-01 21:23:49 -04:00
parent 825df04a35
commit 8d4d5c74e5
3 changed files with 92 additions and 36 deletions

View File

@@ -2,7 +2,10 @@ import fs from "node:fs/promises";
import type { Command } from "commander";
import JSON5 from "json5";
import { readBestEffortConfig, type OpenClawConfig } from "../config/config.js";
import { resolveExecPolicyScopeSnapshot } from "../infra/exec-approvals-effective.js";
import {
collectExecPolicyScopeSnapshots,
type ExecPolicyScopeSnapshot,
} from "../infra/exec-approvals-effective.js";
import {
readExecApprovalsSnapshot,
saveExecApprovals,
@@ -31,7 +34,7 @@ type ConfigSnapshotLike = {
};
type ApprovalsTargetSource = "gateway" | "node" | "local";
type EffectivePolicyReport = {
scopes: ReturnType<typeof collectExecPolicySnapshots>;
scopes: ExecPolicyScopeSnapshot[];
note?: string;
};
@@ -177,37 +180,6 @@ async function loadConfigForApprovalsTarget(params: {
}
}
function collectExecPolicySnapshots(params: { cfg: OpenClawConfig; approvals: ExecApprovalsFile }) {
const snapshots = [
resolveExecPolicyScopeSnapshot({
approvals: params.approvals,
scopeExecConfig: params.cfg.tools?.exec,
configPath: "tools.exec",
scopeLabel: "tools.exec",
}),
];
const globalExecConfig = params.cfg.tools?.exec;
const configAgentIds = new Set((params.cfg.agents?.list ?? []).map((agent) => agent.id));
const approvalAgentIds = Object.keys(params.approvals.agents ?? {}).filter(
(agentId) => agentId !== "*" && agentId !== "default",
);
const agentIds = Array.from(new Set([...configAgentIds, ...approvalAgentIds])).toSorted();
for (const agentId of agentIds) {
const agentConfig = params.cfg.agents?.list?.find((agent) => agent.id === agentId);
snapshots.push(
resolveExecPolicyScopeSnapshot({
approvals: params.approvals,
scopeExecConfig: agentConfig?.tools?.exec,
globalExecConfig,
configPath: `agents.list.${agentId}.tools.exec`,
scopeLabel: `agent:${agentId}`,
agentId,
}),
);
}
return snapshots;
}
function buildEffectivePolicyReport(params: {
cfg: OpenClawConfig | null;
source: ApprovalsTargetSource;
@@ -221,7 +193,7 @@ function buildEffectivePolicyReport(params: {
};
}
return {
scopes: collectExecPolicySnapshots({
scopes: collectExecPolicyScopeSnapshots({
cfg: params.cfg,
approvals: params.approvals,
}),
@@ -235,7 +207,7 @@ function buildEffectivePolicyReport(params: {
};
}
return {
scopes: collectExecPolicySnapshots({
scopes: collectExecPolicyScopeSnapshots({
cfg: params.cfg,
approvals: params.approvals,
}),

View File

@@ -1,3 +1,4 @@
import type { OpenClawConfig } from "../config/config.js";
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
import {
DEFAULT_EXEC_APPROVAL_ASK_FALLBACK,
@@ -157,6 +158,40 @@ function formatHostSource(params: {
return resolveHostFieldSource(params);
}
export function collectExecPolicyScopeSnapshots(params: {
cfg: OpenClawConfig;
approvals: ExecApprovalsFile;
}): ExecPolicyScopeSnapshot[] {
const snapshots = [
resolveExecPolicyScopeSnapshot({
approvals: params.approvals,
scopeExecConfig: params.cfg.tools?.exec,
configPath: "tools.exec",
scopeLabel: "tools.exec",
}),
];
const globalExecConfig = params.cfg.tools?.exec;
const configAgentIds = new Set((params.cfg.agents?.list ?? []).map((agent) => agent.id));
const approvalAgentIds = Object.keys(params.approvals.agents ?? {}).filter(
(agentId) => agentId !== "*" && agentId !== "default",
);
const agentIds = Array.from(new Set([...configAgentIds, ...approvalAgentIds])).toSorted();
for (const agentId of agentIds) {
const agentConfig = params.cfg.agents?.list?.find((agent) => agent.id === agentId);
snapshots.push(
resolveExecPolicyScopeSnapshot({
approvals: params.approvals,
scopeExecConfig: agentConfig?.tools?.exec,
globalExecConfig,
configPath: `agents.list.${agentId}.tools.exec`,
scopeLabel: `agent:${agentId}`,
agentId,
}),
);
}
return snapshots;
}
export function resolveExecPolicyScopeSummary(params: {
approvals: ExecApprovalsFile;
scopeExecConfig?: ExecPolicyConfig | undefined;

View File

@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { resolveExecPolicyScopeSummary } from "./exec-approvals-effective.js";
import type { OpenClawConfig } from "../config/config.js";
import {
collectExecPolicyScopeSnapshots,
resolveExecPolicyScopeSummary,
} from "./exec-approvals-effective.js";
import {
makeMockCommandResolution,
makeMockExecutableResolution,
@@ -341,4 +345,49 @@ describe("exec approvals policy helpers", () => {
source: "OpenClaw default (deny)",
});
});
it("collects global, configured-agent, and approvals-only agent scopes", () => {
const snapshots = collectExecPolicyScopeSnapshots({
cfg: {
tools: {
exec: {
security: "full",
ask: "off",
},
},
agents: {
list: [{ id: "runner" }],
},
} satisfies OpenClawConfig,
approvals: {
version: 1,
agents: {
runner: {
security: "allowlist",
},
batch: {
ask: "always",
},
},
},
});
expect(snapshots.map((snapshot) => snapshot.scopeLabel)).toEqual([
"tools.exec",
"agent:batch",
"agent:runner",
]);
expect(snapshots[1]?.ask).toMatchObject({
requested: "off",
requestedSource: "tools.exec.ask",
host: "always",
effective: "always",
});
expect(snapshots[2]?.security).toMatchObject({
requested: "full",
requestedSource: "tools.exec.security",
host: "allowlist",
effective: "allowlist",
});
});
});