mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-23 22:55:24 +00:00
Exec approvals: fix policy source attribution (#59367)
Merged via squash.
Prepared head SHA: 974945a9f0
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
committed by
GitHub
parent
ad6e42906f
commit
f69570f820
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
- WhatsApp/media: add HTML, XML, and CSS to the MIME map and fall back gracefully for unknown media types instead of dropping the attachment. (#51562) Thanks @bobbyt74.
|
||||
- WhatsApp/presence: send `unavailable` presence on connect in self-chat mode so personal-phone users stop losing all push notifications while the gateway is running. (#59410) Thanks @mcaxtr.
|
||||
- Providers/OpenAI-compatible routing: centralize native-vs-proxy request policy so hidden attribution and related OpenAI-family defaults only apply on verified native endpoints across stream, websocket, and shared audio HTTP paths. (#59433) Thanks @vincentkoc.
|
||||
- Exec approvals/doctor: report host policy sources from the real approvals file path and ignore malformed host override values when attributing effective policy conflicts. (#59367) Thanks @gumadeiras.
|
||||
|
||||
## 2026.4.1-beta.1
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ function createExecApprovals(): ExecApprovalsResolved {
|
||||
askFallback: "full",
|
||||
autoAllowSkills: false,
|
||||
},
|
||||
agentSources: {
|
||||
security: "defaults.security",
|
||||
ask: "defaults.ask",
|
||||
askFallback: "defaults.askFallback",
|
||||
},
|
||||
allowlist: [],
|
||||
file: {
|
||||
version: 1,
|
||||
|
||||
@@ -52,6 +52,11 @@ vi.mock("../infra/exec-approvals.js", async (importOriginal) => {
|
||||
askFallback: "deny",
|
||||
autoAllowSkills: false,
|
||||
},
|
||||
agentSources: {
|
||||
security: "defaults.security",
|
||||
ask: "defaults.ask",
|
||||
askFallback: "defaults.askFallback",
|
||||
},
|
||||
allowlist: [],
|
||||
file: {
|
||||
version: 1,
|
||||
|
||||
@@ -236,13 +236,13 @@ describe("exec approvals CLI", () => {
|
||||
expect.objectContaining({
|
||||
scopeLabel: "agent:runner",
|
||||
security: expect.objectContaining({
|
||||
hostSource: "~/.openclaw/exec-approvals.json agents.*.security",
|
||||
hostSource: "/tmp/local-exec-approvals.json agents.*.security",
|
||||
}),
|
||||
ask: expect.objectContaining({
|
||||
hostSource: "~/.openclaw/exec-approvals.json agents.*.ask",
|
||||
hostSource: "/tmp/local-exec-approvals.json agents.*.ask",
|
||||
}),
|
||||
askFallback: expect.objectContaining({
|
||||
source: "~/.openclaw/exec-approvals.json agents.*.askFallback",
|
||||
source: "/tmp/local-exec-approvals.json agents.*.askFallback",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
@@ -304,7 +304,7 @@ describe("exec approvals CLI", () => {
|
||||
}),
|
||||
askFallback: expect.objectContaining({
|
||||
effective: "deny",
|
||||
source: "~/.openclaw/exec-approvals.json defaults.askFallback",
|
||||
source: "/tmp/node-exec-approvals.json defaults.askFallback",
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -184,6 +184,7 @@ function buildEffectivePolicyReport(params: {
|
||||
cfg: OpenClawConfig | null;
|
||||
source: ApprovalsTargetSource;
|
||||
approvals: ExecApprovalsFile;
|
||||
hostPath: string;
|
||||
}): EffectivePolicyReport {
|
||||
if (params.source === "node") {
|
||||
if (!params.cfg) {
|
||||
@@ -196,6 +197,7 @@ function buildEffectivePolicyReport(params: {
|
||||
scopes: collectExecPolicyScopeSnapshots({
|
||||
cfg: params.cfg,
|
||||
approvals: params.approvals,
|
||||
hostPath: params.hostPath,
|
||||
}),
|
||||
note: "Effective exec policy is the node host approvals file intersected with gateway tools.exec policy.",
|
||||
};
|
||||
@@ -210,6 +212,7 @@ function buildEffectivePolicyReport(params: {
|
||||
scopes: collectExecPolicyScopeSnapshots({
|
||||
cfg: params.cfg,
|
||||
approvals: params.approvals,
|
||||
hostPath: params.hostPath,
|
||||
}),
|
||||
note: "Effective exec policy is the host approvals file intersected with requested tools.exec policy.",
|
||||
};
|
||||
@@ -474,6 +477,7 @@ export function registerExecApprovalsCli(program: Command) {
|
||||
cfg,
|
||||
source,
|
||||
approvals: snapshot.file,
|
||||
hostPath: snapshot.path,
|
||||
});
|
||||
if (opts.json) {
|
||||
defaultRuntime.writeJson({ ...snapshot, effectivePolicy }, 0);
|
||||
|
||||
@@ -343,6 +343,39 @@ describe("noteSecurityWarnings gateway exposure", () => {
|
||||
expect(message).toContain('agents.runner.ask="always"');
|
||||
});
|
||||
|
||||
it("ignores malformed host policy fields when attributing doctor conflicts", async () => {
|
||||
await withExecApprovalsFile(
|
||||
{
|
||||
version: 1,
|
||||
defaults: {
|
||||
ask: "always",
|
||||
},
|
||||
agents: {
|
||||
runner: {
|
||||
ask: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
await noteSecurityWarnings({
|
||||
tools: {
|
||||
exec: {
|
||||
ask: "off",
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [{ id: "runner" }],
|
||||
},
|
||||
} as OpenClawConfig);
|
||||
},
|
||||
);
|
||||
|
||||
const message = lastMessage();
|
||||
expect(message).toContain("agents.list.runner.tools.exec is broader than the host exec policy");
|
||||
expect(message).toContain('defaults.ask="always"');
|
||||
expect(message).not.toContain('agents.runner.ask="foo"');
|
||||
});
|
||||
|
||||
it('does not warn about durable allow-always trust when ask="always" is enforced', async () => {
|
||||
await withExecApprovalsFile(
|
||||
{
|
||||
|
||||
@@ -165,6 +165,92 @@ describe("exec approvals default agent migration", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("exec approvals invalid explicit policy fallback", () => {
|
||||
it("treats invalid explicit agent fields as masked and falls back to defaults instead of wildcard", () => {
|
||||
const resolved = resolveExecApprovalsFromFile({
|
||||
file: {
|
||||
version: 1,
|
||||
defaults: {
|
||||
security: "deny",
|
||||
ask: "on-miss",
|
||||
askFallback: "deny",
|
||||
},
|
||||
agents: {
|
||||
"*": {
|
||||
security: "full",
|
||||
ask: "always",
|
||||
askFallback: "full",
|
||||
},
|
||||
runner: {
|
||||
security: "foo" as unknown as ExecApprovalsAgent["security"],
|
||||
ask: "Always" as unknown as ExecApprovalsAgent["ask"],
|
||||
askFallback: "bar" as unknown as ExecApprovalsAgent["askFallback"],
|
||||
},
|
||||
},
|
||||
},
|
||||
agentId: "runner",
|
||||
overrides: {
|
||||
security: "full",
|
||||
ask: "off",
|
||||
askFallback: "full",
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.agent).toMatchObject({
|
||||
security: "deny",
|
||||
ask: "on-miss",
|
||||
askFallback: "deny",
|
||||
});
|
||||
expect(resolved.agentSources).toEqual({
|
||||
security: "defaults.security",
|
||||
ask: "defaults.ask",
|
||||
askFallback: "defaults.askFallback",
|
||||
});
|
||||
});
|
||||
|
||||
it("treats null explicit agent fields as unset and still considers wildcard", () => {
|
||||
const resolved = resolveExecApprovalsFromFile({
|
||||
file: {
|
||||
version: 1,
|
||||
defaults: {
|
||||
security: "full",
|
||||
ask: "off",
|
||||
askFallback: "full",
|
||||
},
|
||||
agents: {
|
||||
"*": {
|
||||
security: "deny",
|
||||
ask: "always",
|
||||
askFallback: "deny",
|
||||
},
|
||||
runner: {
|
||||
security: null as unknown as ExecApprovalsAgent["security"],
|
||||
ask: null as unknown as ExecApprovalsAgent["ask"],
|
||||
askFallback: null as unknown as ExecApprovalsAgent["askFallback"],
|
||||
},
|
||||
},
|
||||
},
|
||||
agentId: "runner",
|
||||
overrides: {
|
||||
security: "full",
|
||||
ask: "off",
|
||||
askFallback: "full",
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.agent).toMatchObject({
|
||||
security: "deny",
|
||||
ask: "always",
|
||||
askFallback: "deny",
|
||||
});
|
||||
expect(resolved.agentSources).toEqual({
|
||||
security: "agents.*.security",
|
||||
ask: "agents.*.ask",
|
||||
askFallback: "agents.*.askFallback",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeExecApprovals handles string allowlist entries (#9790)", () => {
|
||||
function normalizeMainAllowlist(file: ExecApprovalsFile): ExecAllowlistEntry[] | undefined {
|
||||
const normalized = normalizeExecApprovals(file);
|
||||
|
||||
@@ -62,24 +62,6 @@ function formatRequestedSource(params: {
|
||||
|
||||
type ExecPolicyField = "security" | "ask" | "askFallback";
|
||||
|
||||
function readExecPolicyField(params: {
|
||||
field: ExecPolicyField;
|
||||
entry?: {
|
||||
security?: ExecSecurity;
|
||||
ask?: ExecAsk;
|
||||
askFallback?: ExecSecurity;
|
||||
};
|
||||
}): ExecSecurity | ExecAsk | undefined {
|
||||
switch (params.field) {
|
||||
case "security":
|
||||
return params.entry?.security;
|
||||
case "ask":
|
||||
return params.entry?.ask;
|
||||
case "askFallback":
|
||||
return params.entry?.askFallback;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRequestedField<TValue extends ExecSecurity | ExecAsk>(params: {
|
||||
field: ExecPolicyRequestedField;
|
||||
scopeExecConfig?: ExecPolicyConfig;
|
||||
@@ -89,7 +71,7 @@ function resolveRequestedField<TValue extends ExecSecurity | ExecAsk>(params: {
|
||||
if (scopeValue !== undefined) {
|
||||
return {
|
||||
value: scopeValue as TValue,
|
||||
sourcePath: params.field && "scope",
|
||||
sourcePath: "scope",
|
||||
};
|
||||
}
|
||||
const globalValue = params.globalExecConfig?.[params.field];
|
||||
@@ -106,28 +88,13 @@ function resolveRequestedField<TValue extends ExecSecurity | ExecAsk>(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveHostFieldSource(params: {
|
||||
function formatHostFieldSource(params: {
|
||||
hostPath: string;
|
||||
agentId?: string;
|
||||
field: ExecPolicyField;
|
||||
approvals: ExecApprovalsFile;
|
||||
sourceSuffix: string | null;
|
||||
}): string {
|
||||
const agentKey = params.agentId ?? DEFAULT_AGENT_ID;
|
||||
const explicitAgentEntry = params.approvals.agents?.[agentKey];
|
||||
if (readExecPolicyField({ field: params.field, entry: explicitAgentEntry }) !== undefined) {
|
||||
return `${params.hostPath} agents.${agentKey}.${params.field}`;
|
||||
}
|
||||
const wildcardEntry = params.approvals.agents?.["*"];
|
||||
if (readExecPolicyField({ field: params.field, entry: wildcardEntry }) !== undefined) {
|
||||
return `${params.hostPath} agents.*.${params.field}`;
|
||||
}
|
||||
if (
|
||||
readExecPolicyField({
|
||||
field: params.field,
|
||||
entry: params.approvals.defaults,
|
||||
}) !== undefined
|
||||
) {
|
||||
return `${params.hostPath} defaults.${params.field}`;
|
||||
if (params.sourceSuffix) {
|
||||
return `${params.hostPath} ${params.sourceSuffix}`;
|
||||
}
|
||||
if (params.field === "askFallback") {
|
||||
return `OpenClaw default (${DEFAULT_EXEC_APPROVAL_ASK_FALLBACK})`;
|
||||
@@ -149,24 +116,17 @@ function resolveAskNote(params: {
|
||||
return "more aggressive ask wins";
|
||||
}
|
||||
|
||||
function formatHostSource(params: {
|
||||
hostPath: string;
|
||||
agentId?: string;
|
||||
field: ExecPolicyField;
|
||||
approvals: ExecApprovalsFile;
|
||||
}): string {
|
||||
return resolveHostFieldSource(params);
|
||||
}
|
||||
|
||||
export function collectExecPolicyScopeSnapshots(params: {
|
||||
cfg: OpenClawConfig;
|
||||
approvals: ExecApprovalsFile;
|
||||
hostPath?: string;
|
||||
}): ExecPolicyScopeSnapshot[] {
|
||||
const snapshots = [
|
||||
resolveExecPolicyScopeSnapshot({
|
||||
approvals: params.approvals,
|
||||
scopeExecConfig: params.cfg.tools?.exec,
|
||||
configPath: "tools.exec",
|
||||
hostPath: params.hostPath,
|
||||
scopeLabel: "tools.exec",
|
||||
}),
|
||||
];
|
||||
@@ -188,6 +148,7 @@ export function collectExecPolicyScopeSnapshots(params: {
|
||||
scopeExecConfig: agentConfig?.tools?.exec,
|
||||
globalExecConfig,
|
||||
configPath: `agents.list.${agentId}.tools.exec`,
|
||||
hostPath: params.hostPath,
|
||||
scopeLabel: `agent:${agentId}`,
|
||||
agentId,
|
||||
}),
|
||||
@@ -256,11 +217,10 @@ export function resolveExecPolicyScopeSnapshot(params: {
|
||||
defaultValue: DEFAULT_REQUESTED_SECURITY,
|
||||
}),
|
||||
host: resolved.agent.security,
|
||||
hostSource: formatHostSource({
|
||||
hostSource: formatHostFieldSource({
|
||||
hostPath,
|
||||
agentId: params.agentId,
|
||||
field: "security",
|
||||
approvals: params.approvals,
|
||||
sourceSuffix: resolved.agentSources.security,
|
||||
}),
|
||||
effective: effectiveSecurity,
|
||||
note:
|
||||
@@ -277,11 +237,10 @@ export function resolveExecPolicyScopeSnapshot(params: {
|
||||
defaultValue: DEFAULT_REQUESTED_ASK,
|
||||
}),
|
||||
host: resolved.agent.ask,
|
||||
hostSource: formatHostSource({
|
||||
hostSource: formatHostFieldSource({
|
||||
hostPath,
|
||||
agentId: params.agentId,
|
||||
field: "ask",
|
||||
approvals: params.approvals,
|
||||
sourceSuffix: resolved.agentSources.ask,
|
||||
}),
|
||||
effective: effectiveAsk,
|
||||
note: resolveAskNote({
|
||||
@@ -292,11 +251,10 @@ export function resolveExecPolicyScopeSnapshot(params: {
|
||||
},
|
||||
askFallback: {
|
||||
effective: resolved.agent.askFallback,
|
||||
source: formatHostSource({
|
||||
source: formatHostFieldSource({
|
||||
hostPath,
|
||||
agentId: params.agentId,
|
||||
field: "askFallback",
|
||||
approvals: params.approvals,
|
||||
sourceSuffix: resolved.agentSources.askFallback,
|
||||
}),
|
||||
},
|
||||
allowedDecisions: resolveExecApprovalAllowedDecisions({ ask: effectiveAsk }),
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
hasDurableExecApproval,
|
||||
maxAsk,
|
||||
minSecurity,
|
||||
type ExecApprovalsFile,
|
||||
normalizeExecAsk,
|
||||
normalizeExecHost,
|
||||
normalizeExecTarget,
|
||||
@@ -234,6 +235,33 @@ describe("exec approvals policy helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the actual approvals path when reporting host sources", () => {
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals: {
|
||||
version: 1,
|
||||
defaults: {
|
||||
security: "allowlist",
|
||||
ask: "always",
|
||||
askFallback: "deny",
|
||||
},
|
||||
},
|
||||
scopeExecConfig: {
|
||||
security: "full",
|
||||
ask: "off",
|
||||
},
|
||||
configPath: "tools.exec",
|
||||
scopeLabel: "tools.exec",
|
||||
hostPath: "/tmp/node-exec-approvals.json",
|
||||
});
|
||||
|
||||
expect(summary.security.hostSource).toBe("/tmp/node-exec-approvals.json defaults.security");
|
||||
expect(summary.ask.hostSource).toBe("/tmp/node-exec-approvals.json defaults.ask");
|
||||
expect(summary.askFallback).toEqual({
|
||||
effective: "deny",
|
||||
source: "/tmp/node-exec-approvals.json defaults.askFallback",
|
||||
});
|
||||
});
|
||||
|
||||
it("explains host ask=off suppression separately from stricter ask", () => {
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals: {
|
||||
@@ -257,6 +285,99 @@ describe("exec approvals policy helpers", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("skips malformed host fields when attributing their source", () => {
|
||||
const approvals = {
|
||||
version: 1,
|
||||
defaults: {
|
||||
ask: "always",
|
||||
},
|
||||
agents: {
|
||||
runner: {
|
||||
ask: "foo",
|
||||
},
|
||||
},
|
||||
} as unknown as ExecApprovalsFile;
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals,
|
||||
globalExecConfig: {
|
||||
ask: "off",
|
||||
},
|
||||
configPath: "agents.list.runner.tools.exec",
|
||||
scopeLabel: "agent:runner",
|
||||
agentId: "runner",
|
||||
});
|
||||
|
||||
expect(summary.ask).toMatchObject({
|
||||
requested: "off",
|
||||
host: "always",
|
||||
hostSource: "~/.openclaw/exec-approvals.json defaults.ask",
|
||||
effective: "always",
|
||||
note: "more aggressive ask wins",
|
||||
});
|
||||
});
|
||||
|
||||
it("ignores malformed non-string host fields when attributing their source", () => {
|
||||
const approvals = {
|
||||
version: 1,
|
||||
defaults: {
|
||||
ask: "always",
|
||||
},
|
||||
agents: {
|
||||
runner: {
|
||||
ask: true,
|
||||
},
|
||||
},
|
||||
} as unknown as ExecApprovalsFile;
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals,
|
||||
globalExecConfig: {
|
||||
ask: "off",
|
||||
},
|
||||
configPath: "agents.list.runner.tools.exec",
|
||||
scopeLabel: "agent:runner",
|
||||
agentId: "runner",
|
||||
});
|
||||
|
||||
expect(summary.ask).toMatchObject({
|
||||
requested: "off",
|
||||
host: "always",
|
||||
hostSource: "~/.openclaw/exec-approvals.json defaults.ask",
|
||||
effective: "always",
|
||||
note: "more aggressive ask wins",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not credit mixed-case host fields that resolution ignores", () => {
|
||||
const approvals = {
|
||||
version: 1,
|
||||
defaults: {
|
||||
ask: "always",
|
||||
},
|
||||
agents: {
|
||||
runner: {
|
||||
ask: "Always",
|
||||
},
|
||||
},
|
||||
} as unknown as ExecApprovalsFile;
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals,
|
||||
globalExecConfig: {
|
||||
ask: "off",
|
||||
},
|
||||
configPath: "agents.list.runner.tools.exec",
|
||||
scopeLabel: "agent:runner",
|
||||
agentId: "runner",
|
||||
});
|
||||
|
||||
expect(summary.ask).toMatchObject({
|
||||
requested: "off",
|
||||
host: "always",
|
||||
hostSource: "~/.openclaw/exec-approvals.json defaults.ask",
|
||||
effective: "always",
|
||||
note: "more aggressive ask wins",
|
||||
});
|
||||
});
|
||||
|
||||
it("attributes host policy to wildcard agent entries before defaults", () => {
|
||||
const summary = resolveExecPolicyScopeSummary({
|
||||
approvals: {
|
||||
|
||||
@@ -151,6 +151,11 @@ export type ExecApprovalsResolved = {
|
||||
token: string;
|
||||
defaults: Required<ExecApprovalsDefaults>;
|
||||
agent: Required<ExecApprovalsDefaults>;
|
||||
agentSources: {
|
||||
security: string | null;
|
||||
ask: string | null;
|
||||
askFallback: string | null;
|
||||
};
|
||||
allowlist: ExecAllowlistEntry[];
|
||||
file: ExecApprovalsFile;
|
||||
};
|
||||
@@ -419,18 +424,127 @@ export function ensureExecApprovals(): ExecApprovalsFile {
|
||||
return updated;
|
||||
}
|
||||
|
||||
function normalizeSecurity(value: ExecSecurity | undefined, fallback: ExecSecurity): ExecSecurity {
|
||||
if (value === "allowlist" || value === "full" || value === "deny") {
|
||||
return value;
|
||||
}
|
||||
return fallback;
|
||||
function isExecSecurity(value: unknown): value is ExecSecurity {
|
||||
return value === "allowlist" || value === "full" || value === "deny";
|
||||
}
|
||||
|
||||
function normalizeAsk(value: ExecAsk | undefined, fallback: ExecAsk): ExecAsk {
|
||||
if (value === "always" || value === "off" || value === "on-miss") {
|
||||
return value;
|
||||
function isExecAsk(value: unknown): value is ExecAsk {
|
||||
return value === "always" || value === "off" || value === "on-miss";
|
||||
}
|
||||
|
||||
function normalizeSecurity(value: unknown, fallback: ExecSecurity): ExecSecurity {
|
||||
return isExecSecurity(value) ? value : fallback;
|
||||
}
|
||||
|
||||
function normalizeAsk(value: unknown, fallback: ExecAsk): ExecAsk {
|
||||
return isExecAsk(value) ? value : fallback;
|
||||
}
|
||||
|
||||
type ResolvedExecPolicyField<TValue extends ExecSecurity | ExecAsk> = {
|
||||
value: TValue;
|
||||
source: string | null;
|
||||
};
|
||||
|
||||
function resolveDefaultSecurityField(params: {
|
||||
field: "security" | "askFallback";
|
||||
defaults: ExecApprovalsDefaults;
|
||||
fallback: ExecSecurity;
|
||||
}): ResolvedExecPolicyField<ExecSecurity> {
|
||||
const defaultValue = params.defaults[params.field];
|
||||
if (isExecSecurity(defaultValue)) {
|
||||
return {
|
||||
value: defaultValue,
|
||||
source: `defaults.${params.field}`,
|
||||
};
|
||||
}
|
||||
return fallback;
|
||||
return {
|
||||
value: params.fallback,
|
||||
source: null,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveDefaultAskField(params: {
|
||||
defaults: ExecApprovalsDefaults;
|
||||
fallback: ExecAsk;
|
||||
}): ResolvedExecPolicyField<ExecAsk> {
|
||||
if (isExecAsk(params.defaults.ask)) {
|
||||
return {
|
||||
value: params.defaults.ask,
|
||||
source: "defaults.ask",
|
||||
};
|
||||
}
|
||||
return {
|
||||
value: params.fallback,
|
||||
source: null,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveAgentSecurityField(params: {
|
||||
field: "security" | "askFallback";
|
||||
defaults: ExecApprovalsDefaults;
|
||||
agent: ExecApprovalsAgent;
|
||||
wildcard: ExecApprovalsAgent;
|
||||
agentKey: string;
|
||||
fallback: ExecSecurity;
|
||||
}): ResolvedExecPolicyField<ExecSecurity> {
|
||||
const fallbackField = resolveDefaultSecurityField({
|
||||
field: params.field,
|
||||
defaults: params.defaults,
|
||||
fallback: params.fallback,
|
||||
});
|
||||
const agentValue = params.agent[params.field];
|
||||
if (agentValue != null) {
|
||||
if (isExecSecurity(agentValue)) {
|
||||
return {
|
||||
value: agentValue,
|
||||
source: `agents.${params.agentKey}.${params.field}`,
|
||||
};
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
const wildcardValue = params.wildcard[params.field];
|
||||
if (wildcardValue != null) {
|
||||
if (isExecSecurity(wildcardValue)) {
|
||||
return {
|
||||
value: wildcardValue,
|
||||
source: `agents.*.${params.field}`,
|
||||
};
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
|
||||
function resolveAgentAskField(params: {
|
||||
defaults: ExecApprovalsDefaults;
|
||||
agent: ExecApprovalsAgent;
|
||||
wildcard: ExecApprovalsAgent;
|
||||
agentKey: string;
|
||||
fallback: ExecAsk;
|
||||
}): ResolvedExecPolicyField<ExecAsk> {
|
||||
const fallbackField = resolveDefaultAskField({
|
||||
defaults: params.defaults,
|
||||
fallback: params.fallback,
|
||||
});
|
||||
if (params.agent.ask != null) {
|
||||
if (isExecAsk(params.agent.ask)) {
|
||||
return {
|
||||
value: params.agent.ask,
|
||||
source: `agents.${params.agentKey}.ask`,
|
||||
};
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
if (params.wildcard.ask != null) {
|
||||
if (isExecAsk(params.wildcard.ask)) {
|
||||
return {
|
||||
value: params.wildcard.ask,
|
||||
source: "agents.*.ask",
|
||||
};
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
return fallbackField;
|
||||
}
|
||||
|
||||
export type ExecApprovalsDefaultOverrides = {
|
||||
@@ -481,16 +595,33 @@ export function resolveExecApprovalsFromFile(params: {
|
||||
),
|
||||
autoAllowSkills: Boolean(defaults.autoAllowSkills ?? fallbackAutoAllowSkills),
|
||||
};
|
||||
const resolvedAgentSecurity = resolveAgentSecurityField({
|
||||
field: "security",
|
||||
defaults,
|
||||
agent,
|
||||
wildcard,
|
||||
agentKey,
|
||||
fallback: resolvedDefaults.security,
|
||||
});
|
||||
const resolvedAgentAsk = resolveAgentAskField({
|
||||
defaults,
|
||||
agent,
|
||||
wildcard,
|
||||
agentKey,
|
||||
fallback: resolvedDefaults.ask,
|
||||
});
|
||||
const resolvedAgentAskFallback = resolveAgentSecurityField({
|
||||
field: "askFallback",
|
||||
defaults,
|
||||
agent,
|
||||
wildcard,
|
||||
agentKey,
|
||||
fallback: resolvedDefaults.askFallback,
|
||||
});
|
||||
const resolvedAgent: Required<ExecApprovalsDefaults> = {
|
||||
security: normalizeSecurity(
|
||||
agent.security ?? wildcard.security ?? resolvedDefaults.security,
|
||||
resolvedDefaults.security,
|
||||
),
|
||||
ask: normalizeAsk(agent.ask ?? wildcard.ask ?? resolvedDefaults.ask, resolvedDefaults.ask),
|
||||
askFallback: normalizeSecurity(
|
||||
agent.askFallback ?? wildcard.askFallback ?? resolvedDefaults.askFallback,
|
||||
resolvedDefaults.askFallback,
|
||||
),
|
||||
security: resolvedAgentSecurity.value,
|
||||
ask: resolvedAgentAsk.value,
|
||||
askFallback: resolvedAgentAskFallback.value,
|
||||
autoAllowSkills: Boolean(
|
||||
agent.autoAllowSkills ?? wildcard.autoAllowSkills ?? resolvedDefaults.autoAllowSkills,
|
||||
),
|
||||
@@ -507,6 +638,11 @@ export function resolveExecApprovalsFromFile(params: {
|
||||
token: params.token ?? file.socket?.token ?? "",
|
||||
defaults: resolvedDefaults,
|
||||
agent: resolvedAgent,
|
||||
agentSources: {
|
||||
security: resolvedAgentSecurity.source,
|
||||
ask: resolvedAgentAsk.source,
|
||||
askFallback: resolvedAgentAskFallback.source,
|
||||
},
|
||||
allowlist,
|
||||
file,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user