mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
feat(exec): default host exec to yolo
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user