feat(exec): default host exec to yolo

This commit is contained in:
Peter Steinberger
2026-04-02 14:50:31 +01:00
parent 0500b410c5
commit c678ae7e7a
13 changed files with 144 additions and 32 deletions

View File

@@ -168,7 +168,7 @@ export async function executeNodeHostCommand(
const resolved = resolveExecApprovalsFromFile({
file: approvalsFile as ExecApprovalsFile,
agentId: params.agentId,
overrides: { security: "allowlist" },
overrides: { security: "full" },
});
// Allowlist-only precheck; safe bins are node-local and may diverge.
const allowlistEval = evaluateShellAllowlist({

View File

@@ -115,7 +115,7 @@ function expectPendingApprovalText(
expect(pendingText).toContain(
(options.allowedDecisions ?? "").includes("allow-always")
? "Background mode requires pre-approved policy"
: "Background mode requires host policy that allows pre-approval",
: "Background mode requires an effective policy that allows pre-approval",
);
}
return details;
@@ -339,6 +339,7 @@ describe("exec approvals", () => {
const tool = createExecTool({
host: "node",
security: "allowlist",
ask: "on-miss",
approvalRunningNoticeMs: 0,
});
@@ -1131,7 +1132,7 @@ describe("exec approvals", () => {
const tool = createExecTool({
host: "gateway",
ask: "always",
security: "full",
security: "allowlist",
trigger: "cron",
approvalRunningNoticeMs: 0,
});
@@ -1211,6 +1212,11 @@ describe("exec approvals", () => {
});
it("explains cron no-route denials with a host-policy fix hint", async () => {
await writeExecApprovalsConfig({
version: 1,
defaults: { security: "full", ask: "always", askFallback: "deny" },
agents: {},
});
mockNoApprovalRouteRegistration();
const tool = createExecTool({

View File

@@ -596,16 +596,14 @@ export function createExecTool(
const approvalDefaults = loadExecApprovals().defaults;
const configuredSecurity =
defaults?.security ??
approvalDefaults?.security ??
(host === "sandbox" ? "deny" : "allowlist");
defaults?.security ?? approvalDefaults?.security ?? (host === "sandbox" ? "deny" : "full");
const requestedSecurity = normalizeExecSecurity(params.security);
let security = minSecurity(configuredSecurity, requestedSecurity ?? configuredSecurity);
if (elevatedRequested && elevatedMode === "full") {
security = "full";
}
// Keep local exec defaults in sync with exec-approvals.json when tools.exec.* is unset.
const configuredAsk = defaults?.ask ?? approvalDefaults?.ask ?? "on-miss";
const configuredAsk = defaults?.ask ?? approvalDefaults?.ask ?? "off";
const requestedAsk = normalizeExecAsk(params.ask);
let ask = maxAsk(configuredAsk, requestedAsk ?? configuredAsk);
const bypassApprovals = elevatedRequested && elevatedMode === "full";

View File

@@ -75,8 +75,8 @@ function execAskRank(value: ExecAsk): number {
function collectExecPolicyConflictWarnings(cfg: OpenClawConfig): string[] {
const warnings: string[] = [];
const approvals = loadExecApprovals();
const defaultRequestedSecuritySource = "OpenClaw default (allowlist)";
const defaultRequestedAskSource = "OpenClaw default (on-miss)";
const defaultRequestedSecuritySource = "OpenClaw default (full)";
const defaultRequestedAskSource = "OpenClaw default (off)";
const maybeWarn = (params: {
scopeLabel: string;

View File

@@ -432,12 +432,12 @@ describe("normalizeExecApprovals strips invalid security/ask enum values (#59006
},
} as unknown as ExecApprovalsFile;
const resolved = resolveExecApprovalsFromFile({ file });
// Invalid "none" in defaults is stripped, so fallback to DEFAULT_SECURITY ("deny")
expect(resolved.defaults.security).toBe("deny");
// Invalid "never" in defaults is stripped, so fallback to DEFAULT_ASK ("on-miss")
expect(resolved.defaults.ask).toBe("on-miss");
// Invalid "none" in defaults is stripped, so fallback to DEFAULT_SECURITY ("full")
expect(resolved.defaults.security).toBe("full");
// Invalid "never" in defaults is stripped, so fallback to DEFAULT_ASK ("off")
expect(resolved.defaults.ask).toBe("off");
// Wildcard agent "none" is stripped, so agent inherits resolved defaults
expect(resolved.agent.security).toBe("deny");
expect(resolved.agent.security).toBe("full");
// Wildcard agent ask="off" is valid and preserved
expect(resolved.agent.ask).toBe("off");
});

View File

@@ -12,8 +12,8 @@ import {
type ExecSecurity,
} from "./exec-approvals.js";
const DEFAULT_REQUESTED_SECURITY: ExecSecurity = "allowlist";
const DEFAULT_REQUESTED_ASK: ExecAsk = "on-miss";
const DEFAULT_REQUESTED_SECURITY: ExecSecurity = "full";
const DEFAULT_REQUESTED_ASK: ExecAsk = "off";
const DEFAULT_HOST_PATH = "~/.openclaw/exec-approvals.json";
const REQUESTED_DEFAULT_LABEL = {
security: DEFAULT_REQUESTED_SECURITY,

View File

@@ -463,8 +463,8 @@ describe("exec approvals policy helpers", () => {
});
expect(summary.askFallback).toEqual({
effective: "deny",
source: "OpenClaw default (deny)",
effective: "full",
source: "OpenClaw default (full)",
});
});

View File

@@ -168,9 +168,9 @@ export type ExecApprovalsResolved = {
// Keep CLI + gateway defaults in sync.
export const DEFAULT_EXEC_APPROVAL_TIMEOUT_MS = 1_800_000;
const DEFAULT_SECURITY: ExecSecurity = "deny";
const DEFAULT_ASK: ExecAsk = "on-miss";
export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "deny";
const DEFAULT_SECURITY: ExecSecurity = "full";
const DEFAULT_ASK: ExecAsk = "off";
export const DEFAULT_EXEC_APPROVAL_ASK_FALLBACK: ExecSecurity = "full";
const DEFAULT_AUTO_ALLOW_SKILLS = false;
const DEFAULT_SOCKET = "~/.openclaw/exec-approvals.sock";
const DEFAULT_FILE = "~/.openclaw/exec-approvals.json";
@@ -304,9 +304,7 @@ function sanitizeExecApprovalPolicy(
const askFallback = toStringOrUndefined(policy?.askFallback)?.trim();
return {
security:
security === "deny" || security === "allowlist" || security === "full"
? security
: undefined,
security === "deny" || security === "allowlist" || security === "full" ? security : undefined,
ask: ask === "off" || ask === "on-miss" || ask === "always" ? ask : undefined,
askFallback:
askFallback === "deny" || askFallback === "allowlist" || askFallback === "full"