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
+5
View File
@@ -11,6 +11,11 @@ Docs: https://docs.openclaw.ai
### Changes
- Exec defaults: make gateway/node host exec default to YOLO mode by requesting `security=full` with `ask=off`, and align host approval-file fallbacks plus docs/doctor reporting with that no-prompt default.
- Agents/compaction: add `agents.defaults.compaction.notifyUser` so the `🧹 Compacting context...` start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327.
- Plugins/hooks: add `before_agent_reply` so plugins can short-circuit the LLM with synthetic replies after inline actions. (#20067) Thanks @JoshuaLelon
- Providers/runtime: add provider-owned replay hook surfaces for transcript policy, replay cleanup, and reasoning-mode dispatch. (#59143) Thanks @jalehman.
- Diffs: add plugin-owned `viewerBaseUrl` so viewer links can use a stable proxy/public origin without passing `baseUrl` on every tool call. (#59341) Related #59227. Thanks @gumadeiras.
- Matrix/plugin: emit spec-compliant `m.mentions` metadata across text sends, media captions, edits, poll fallback text, and action-driven edits so Matrix mentions notify reliably in clients like Element. (#59323) Thanks @gumadeiras.
- Feishu/comments: add a dedicated Drive comment-event flow with comment-thread context resolution, in-thread replies, and `feishu_drive` comment actions for document collaboration workflows. (#58497) thanks @wittam-01.
- WhatsApp/reactions: add `reactionLevel` guidance for agent reactions. Thanks @mcaxtr.
+42
View File
@@ -45,6 +45,48 @@ openclaw approvals set --node <id|name|ip> --file ./exec-approvals.json
openclaw approvals set --gateway --file ./exec-approvals.json
```
## "Never prompt" / YOLO example
For a host that should never stop on exec approvals, set the host approvals defaults to `full` + `off`:
```bash
openclaw approvals set --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
Node variant:
```bash
openclaw approvals set --node <id|name|ip> --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
This changes the **host approvals file** only. To keep the requested OpenClaw policy aligned, also set:
```bash
openclaw config set tools.exec.host gateway
openclaw config set tools.exec.security full
openclaw config set tools.exec.ask off
```
This matches the current host-default YOLO behavior. Tighten it if you want approvals.
## Allowlist helpers
```bash
+6 -6
View File
@@ -278,11 +278,11 @@ flowchart TD
- If `tools.exec.host` is unset, the default is `auto`.
- `host=auto` resolves to `sandbox` when a sandbox runtime is active, `gateway` otherwise.
- On `gateway` and `node`, unset `tools.exec.security` defaults to `allowlist`.
- Unset `tools.exec.ask` defaults to `on-miss`.
- Result: ordinary host commands can now pause with `Approval required` instead of running immediately.
- On `gateway` and `node`, unset `tools.exec.security` defaults to `full`.
- Unset `tools.exec.ask` defaults to `off`.
- Result: if you are seeing approvals, some host-local or per-session policy tightened exec away from the current defaults.
Restore the old gateway no-approval behavior:
Restore current default no-approval behavior:
```bash
openclaw config set tools.exec.host gateway
@@ -293,8 +293,8 @@ flowchart TD
Safer alternatives:
- Set only `tools.exec.host=gateway` if you just want stable host routing and still want approvals.
- Keep `security=allowlist` with `ask=on-miss` if you want host exec but still want review on allowlist misses.
- Set only `tools.exec.host=gateway` if you just want stable host routing.
- Use `security=allowlist` with `ask=on-miss` if you want host exec but still want review on allowlist misses.
- Enable sandbox mode if you want `host=auto` to resolve back to `sandbox`.
Common log signatures:
+62
View File
@@ -91,6 +91,68 @@ Example schema:
}
```
## No-approval "YOLO" mode
If you want host exec to run without approval prompts, you must open **both** policy layers:
- requested exec policy in OpenClaw config (`tools.exec.*`)
- host-local approvals policy in `~/.openclaw/exec-approvals.json`
This is now the default host behavior unless you tighten it explicitly:
- `tools.exec.security`: `full` on `gateway`/`node`
- `tools.exec.ask`: `off`
- host `askFallback`: `full`
If you want a more conservative setup, tighten either layer back to `allowlist` / `on-miss`
or `deny`.
Persistent gateway-host "never prompt" setup:
```bash
openclaw config set tools.exec.host gateway
openclaw config set tools.exec.security full
openclaw config set tools.exec.ask off
openclaw gateway restart
```
Then set the host approvals file to match:
```bash
openclaw approvals set --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
For a node host, apply the same approvals file on that node instead:
```bash
openclaw approvals set --node <id|name|ip> --stdin <<'EOF'
{
version: 1,
defaults: {
security: "full",
ask: "off",
askFallback: "full"
}
}
EOF
```
Session-only shortcut:
- `/exec security=full ask=off` changes only the current session.
- `/elevated full` is a break-glass shortcut that also skips exec approvals for that session.
If the host approvals file stays stricter than config, the stricter host policy still wins.
## Policy knobs
### Security (`exec.security`)
+3 -2
View File
@@ -54,8 +54,9 @@ Notes:
- `tools.exec.notifyOnExit` (default: true): when true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit.
- `tools.exec.approvalRunningNoticeMs` (default: 10000): emit a single “running” notice when an approval-gated exec runs longer than this (0 disables).
- `tools.exec.host` (default: `auto`; resolves to `sandbox` when sandbox runtime is active, `gateway` otherwise)
- `tools.exec.security` (default: `deny` for sandbox, `allowlist` for gateway + node when unset)
- `tools.exec.ask` (default: `on-miss`)
- `tools.exec.security` (default: `deny` for sandbox, `full` for gateway + node when unset)
- `tools.exec.ask` (default: `off`)
- No-approval host exec is the default for gateway + node. If you want approvals/allowlist behavior, tighten both `tools.exec.*` and the host `~/.openclaw/exec-approvals.json`; see [Exec approvals](/tools/exec-approvals#no-approval-yolo-mode).
- `tools.exec.node` (default: unset)
- `tools.exec.strictInlineEval` (default: false): when true, inline interpreter eval forms such as `python -c`, `node -e`, `ruby -e`, `perl -e`, `php -r`, `lua -e`, and `osascript -e` always require explicit approval. `allow-always` can still persist benign interpreter/script invocations, but inline-eval forms still prompt each time.
- `tools.exec.pathPrepend`: list of directories to prepend to `PATH` for exec runs (gateway + sandbox only).
+1 -1
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({
@@ -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({
+2 -4
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";
+2 -2
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;
+5 -5
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");
});
+2 -2
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,
+2 -2
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)",
});
});
+4 -6
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"