Skip to content

fix: align claude cli permissions with exec policy#70800

Merged
steipete merged 1 commit into
mainfrom
codex/claude-cli-exec-policy-followup
Apr 23, 2026
Merged

fix: align claude cli permissions with exec policy#70800
steipete merged 1 commit into
mainfrom
codex/claude-cli-exec-policy-followup

Conversation

@steipete

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Security: remove claude-cli permission bypass defaults #70723's direct landing removed Claude CLI bypass defaults, but did not model the intended default YOLO behavior against OpenClaw's existing exec policy.
  • Why it matters: Claude CLI permission behavior should match Codex/harness safety settings and remain configurable without adding provider-specific config keys.
  • What changed: Claude CLI now injects --permission-mode bypassPermissions only when tools.exec.security=full and tools.exec.ask=off, including per-agent agents.list[].tools.exec; explicit raw Claude args still override; malformed permission-mode args are stripped.
  • What did NOT change (scope boundary): no new Claude-specific config options; no changes to host exec approval enforcement.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: Claude CLI permission defaults were modeled as provider-specific behavior instead of deriving from the existing OpenClaw exec policy.
  • Missing detection / guardrail: coverage did not assert per-agent exec policy propagation into CLI backend normalization.
  • Contributing context: Security: remove claude-cli permission bypass defaults #70723 was closed after a direct landing; this is the follow-up that preserves the default YOLO semantics while avoiding new config fields.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/anthropic/cli-shared.test.ts, src/agents/cli-backends.test.ts, src/gateway/gateway-cli-backend.live.test.ts
  • Scenario the test should lock in: global and per-agent exec policy control Claude CLI permission-mode injection; raw Claude args override defaults.
  • Why this is the smallest reliable guardrail: normalization is the policy boundary, with one live smoke proving Claude CLI remains noninteractive.
  • Existing test that already covers this (if any): existing CLI backend normalization tests, extended here.
  • If no new test is added, why not: N/A

User-visible / Behavior Changes

Claude CLI follows OpenClaw's existing YOLO exec policy by adding --permission-mode bypassPermissions only for security=full + ask=off. Safer exec policy omits bypass. Users can still force Claude behavior with explicit raw --permission-mode args.

Diagram (if applicable)

Before:
[claude-cli defaults] -> [permission behavior drifted from OpenClaw exec policy]

After:
[tools.exec / per-agent tools.exec] -> [claude-cli normalizeConfig] -> [permission-mode args]

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? Yes
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: Claude CLI bypass mode is now tied to OpenClaw's existing YOLO exec policy and remains opt-out via explicit raw Claude args. Safer exec policies do not receive bypass.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local Node/pnpm
  • Model/provider: claude-cli/claude-sonnet-4-6
  • Integration/channel (if any): Gateway live CLI backend
  • Relevant config (redacted): ANTHROPIC_API_KEY from ~/.profile; token env unset

Steps

  1. Configure default YOLO exec policy or rely on OpenClaw defaults.
  2. Resolve Claude CLI backend config.
  3. Run live Claude CLI backend smoke.

Expected

  • YOLO config injects --permission-mode bypassPermissions.
  • Safer exec policy omits bypass.
  • Explicit raw --permission-mode args override.
  • Claude CLI returns noninteractively.

Actual

  • Matches expected.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

  • Verified scenarios: focused unit tests; config and plugin SDK drift checks; pnpm build; direct Claude non-bypass probe; live OpenClaw Claude CLI smoke with debug args showing --permission-mode bypassPermissions.
  • Edge cases checked: malformed permission-mode args stripped; explicit raw args preserved; per-agent exec policy overrides global policy.
  • What you did not verify: full broad changed-test lane to completion; it exited 143 after known Vitest no-output watchdog stalls without assertion failures.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: Claude CLI flag compatibility could change upstream.
    • Mitigation: live probe covered Claude Code 2.1.12; explicit raw args remain available for override.

@aisle-research-bot

aisle-research-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 3 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Arbitrary local image file exfiltration via prompt path references when using Claude CLI imageArg '@'
2 🟠 High Claude CLI backend enables all OpenClaw MCP tools via wildcard allowlist (can self-approve exec/plugin requests)
3 🟡 Medium Claude CLI permission bypass inferred from missing exec policy, ignoring sandbox default security
1. 🟠 Arbitrary local image file exfiltration via prompt path references when using Claude CLI imageArg '@'
Property Value
Severity High
CWE CWE-200
Location src/agents/cli-runner/helpers.ts:323-346

Description

The CLI runner auto-detects image file path references inside the prompt text (including absolute paths like /path/to/image.png, ~/Pictures/..., and file://...) and loads them from disk without enforcing workspaceOnly. With the new Anthropic backend defaults imageArg: "@", the runner then prefixes the emitted paths with @, which Claude CLI interprets as “attach this local file as an image”.

This enables a prompt-injection style data exfiltration of any local image file readable by the OpenClaw process (e.g., screenshots, camera photos, exported diagrams containing secrets), because an untrusted prompt can include an image-looking absolute path and the host will read and attach it.

Key points:

  • Input: untrusted prompt text (LLM/user-controlled) containing a path ending in an image extension.
  • File read: loadPromptRefImages()loadImageFromRef() loads from filesystem.
  • Missing guard: loadPromptRefImages() is called without workspaceOnly: true, so absolute paths and ../ outside workspace are allowed (unless a sandbox is used elsewhere).
  • Exfil sink: appendImagePathsToPrompt(..., prefix="@") when backend.imageArg === "@" causes Claude CLI to read and send the file contents.

Vulnerable code:

const resolvedImages = ... : await loadPromptRefImages({ prompt, workspaceDir: params.workspaceDir });
...
prompt = appendImagePathsToPrompt(prompt, imagePaths, params.backend.imageArg === "@" ? "@" : "");

Recommendation

Enforce a strict local-file policy for prompt-detected image references.

Minimum safe fix (recommended): default to workspaceOnly: true for CLI backends, unless explicitly opted out.

const resolvedImages =
  params.images && params.images.length > 0
    ? params.images
    : await loadPromptRefImages({
        prompt,
        workspaceDir: params.workspaceDir,
        workspaceOnly: true, // prevent absolute paths / traversal outside workspace
      });

Additionally:

  • Reject absolute paths and file:// refs for CLI backends unless a sandbox/allowlist is configured.
  • If backend.imagePathScope === "workspace", also enforce workspaceOnly: true to align “where images are written” with “what can be read”.
  • Consider a separate config flag like allowPromptImagePaths: false by default, requiring explicit user opt-in for local-path image loading.
2. 🟠 Claude CLI backend enables all OpenClaw MCP tools via wildcard allowlist (can self-approve exec/plugin requests)
Property Value
Severity High
CWE CWE-285
Location extensions/anthropic/cli-backend.ts:31-54

Description

OpenClaw’s Anthropic Claude CLI backend now passes --allowedTools mcp__openclaw__* by default.

This is risky because Claude Code’s --allowedTools is a coarse client-side tool allowlist. The wildcard enables every tool exposed by the openclaw MCP server namespace, including highly sensitive approval-management tools.

In src/mcp/channel-tools.ts, the OpenClaw MCP server registers tools such as permissions_respond, which calls through to bridge.respondToApproval() and ultimately to gateway approval resolution methods:

  • permissions_respond can allow pending exec/plugin approval requests.
  • If the model can invoke this tool, it may be able to approve its own exec/plugin requests, bypassing intended human approval gates.

Vulnerable change (enables the wildcard by default):

"--allowedTools",
"mcp__openclaw__*",

This is especially concerning because the wildcard is applied to both fresh and resumed sessions, so it persists across runs.

Recommendation

Avoid wildcard tool enablement; explicitly allow only the minimal set of MCP tools required.

Option A (preferred): do not set --allowedTools by default; instead, compute a precise list from OpenClaw’s effective tool policy and pass only those names.

Option B: explicitly deny sensitive approval/administrative tools from the MCP server namespace (or move them behind an owner-only gate enforced server-side).

For example, restrict to read-only conversation tools and exclude permissions_respond:

args: [// ...
  "--allowedTools",
  "mcp__openclaw__conversations_list,mcp__openclaw__conversation_get,mcp__openclaw__messages_read"
]

Additionally, enforce authorization server-side for approval resolution regardless of client allowlists (e.g., require an authenticated owner/admin identity for exec.approval.resolve / plugin.approval.resolve).

3. 🟡 Claude CLI permission bypass inferred from missing exec policy, ignoring sandbox default security
Property Value
Severity Medium
CWE CWE-284
Location extensions/anthropic/cli-shared.ts:70-77

Description

isOpenClawRequestedYolo() infers OpenClaw's effective exec policy as YOLO when tools.exec.security / tools.exec.ask are unset, defaulting them to "full" and "off". This can incorrectly enable Claude CLI's noninteractive permission mode (--permission-mode bypassPermissions) even when OpenClaw is configured for sandboxed execution where the documented default security is deny.

Impact:

  • If a user sets tools.exec.host: "sandbox" (or an agent override) but does not explicitly set tools.exec.security, OpenClaw's effective exec policy is expected to be safer (security=deny by default for sandbox).
  • The current inference ignores host and assumes security=full, causing resolveClaudePermissionMode() to return bypassPermissions.
  • normalizeClaudeBackendConfig() then appends --permission-mode bypassPermissions, potentially disabling Claude Code’s interactive permission checks unexpectedly.

Vulnerable code:

const security = exec?.security ?? "full";
const ask = exec?.ask ?? "off";
return security === "full" && ask === "off";

Recommendation

Derive the effective exec policy using the same logic as the runtime (including host-dependent defaults), or at minimum incorporate tools.exec.host when choosing defaults.

For example, compute defaults consistent with /src/agents/exec-defaults.ts (sandbox defaults to deny):

function isOpenClawRequestedYolo(ctx?: CliBackendNormalizeConfigContext): boolean {
  const agentExec = ctx?.agentId
    ? ctx.config?.agents?.list?.find((a) => a.id === ctx.agentId)?.tools?.exec
    : undefined;
  const exec = agentExec ?? ctx?.config?.tools?.exec;

  const host = exec?.host ?? "auto";
  const defaultSecurity = host === "sandbox" ? "deny" : "full";
  const security = exec?.security ?? defaultSecurity;
  const ask = exec?.ask ?? "off";

  return security === "full" && ask === "off";
}

Even better: pass in (or resolve) the already-computed effective exec policy (security/ask/host) from the core exec-policy resolver rather than re-deriving it here, to avoid future drift.


Analyzed PR: #70800 at commit 8f58608

Last updated on: 2026-04-23T22:05:35Z

@cursor

cursor Bot commented Apr 23, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches CLI backend normalization and tool-execution permission behavior by conditionally injecting Claude’s noninteractive --permission-mode bypassPermissions, and also changes the plugin SDK normalizeConfig hook signature to accept config context; misconfiguration could unintentionally relax or tighten CLI execution prompts.

Overview
Aligns the bundled claude-cli backend’s noninteractive permissions with OpenClaw’s existing exec policy: --permission-mode bypassPermissions is now injected only when the effective (global or per-agent) tools.exec policy is YOLO (security: "full" + ask: "off"), while explicit raw --permission-mode args still take precedence and malformed permission flags are stripped.

Extends the CLI-backend plugin API by adding CliBackendNormalizeConfigContext (config + backendId + optional agentId) and threads it through resolveCliBackendConfig/CLI runner so plugins can normalize config based on agent scope; updates tests and docs accordingly. Also bumps Cloudflare AI Gateway’s default model from claude-sonnet-4-5 to claude-sonnet-4-6 and refreshes related baselines/docs.

Reviewed by Cursor Bugbot for commit 8f58608. Bugbot is set up for automated code reviews on this repo. Configure here.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime agents Agent runtime and tooling extensions: anthropic extensions: cloudflare-ai-gateway extensions: qa-lab size: M maintainer Maintainer-authored PR labels Apr 23, 2026
@greptile-apps

greptile-apps Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR aligns Claude CLI's --permission-mode injection with OpenClaw's existing exec policy instead of treating it as a provider-specific default. When tools.exec.security=full and tools.exec.ask=off (the OpenClaw YOLO default), --permission-mode bypassPermissions is injected; all other exec policies leave the flag absent, and explicit raw --permission-mode args always win. The new CliBackendNormalizeConfigContext type threads per-agent exec policy through resolveCliBackendConfig and into normalizeConfig, with the call-site in prepareCliRunContext updated to pass agentId.

Confidence Score: 5/5

Safe to merge; logic is correct, well-tested, and backward-compatible.

All findings are P2 style suggestions (test logic duplication, dead-code field). The core permission-mode injection, per-agent fallback, and raw-arg override semantics are correct and covered by new unit tests. No data-integrity, security-boundary, or runtime-breakage concerns were found.

No files require special attention.

Comments Outside Diff (1)

  1. src/agents/cli-backends.test.ts, line 118-155 (link)

    P2 Test duplicates production YOLO-detection logic

    isTestYoloConfig and normalizeTestPermissionMode in the test file re-implement the same defaulting logic as isOpenClawRequestedYolo / resolveClaudePermissionMode in production. If the default values for security or ask change in cli-shared.ts, this local copy will silently diverge and tests will stop catching the regression they were written to detect. Importing and delegating to the real helpers would keep the test behaviour in lock-step with production.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/agents/cli-backends.test.ts
    Line: 118-155
    
    Comment:
    **Test duplicates production YOLO-detection logic**
    
    `isTestYoloConfig` and `normalizeTestPermissionMode` in the test file re-implement the same defaulting logic as `isOpenClawRequestedYolo` / `resolveClaudePermissionMode` in production. If the default values for `security` or `ask` change in `cli-shared.ts`, this local copy will silently diverge and tests will stop catching the regression they were written to detect. Importing and delegating to the real helpers would keep the test behaviour in lock-step with production.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/cli-backends.test.ts
Line: 118-155

Comment:
**Test duplicates production YOLO-detection logic**

`isTestYoloConfig` and `normalizeTestPermissionMode` in the test file re-implement the same defaulting logic as `isOpenClawRequestedYolo` / `resolveClaudePermissionMode` in production. If the default values for `security` or `ask` change in `cli-shared.ts`, this local copy will silently diverge and tests will stop catching the regression they were written to detect. Importing and delegating to the real helpers would keep the test behaviour in lock-step with production.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/anthropic/cli-shared.ts
Line: 80-87

Comment:
**`overrideExisting: true` path is currently dead code**

`resolveClaudePermissionMode` always returns `overrideExisting: false`, so the branch in `normalizeClaudePermissionArgs` that checks `options.overrideExisting` for true (dropping the caller-supplied value and injecting the policy mode instead) is never exercised by any current caller. If `overrideExisting: true` is intentionally reserved for future callers, a comment explaining the intended use case would help; otherwise the field can be dropped to simplify the contract.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: align claude cli permissions with e..." | Re-trigger Greptile

Comment on lines +80 to +87
export function resolveClaudePermissionMode(context?: CliBackendNormalizeConfigContext): {
mode?: string;
overrideExisting: boolean;
} {
return isOpenClawRequestedYolo(context)
? { mode: CLAUDE_BYPASS_PERMISSION_MODE, overrideExisting: false }
: { overrideExisting: false };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 overrideExisting: true path is currently dead code

resolveClaudePermissionMode always returns overrideExisting: false, so the branch in normalizeClaudePermissionArgs that checks options.overrideExisting for true (dropping the caller-supplied value and injecting the policy mode instead) is never exercised by any current caller. If overrideExisting: true is intentionally reserved for future callers, a comment explaining the intended use case would help; otherwise the field can be dropped to simplify the contract.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/anthropic/cli-shared.ts
Line: 80-87

Comment:
**`overrideExisting: true` path is currently dead code**

`resolveClaudePermissionMode` always returns `overrideExisting: false`, so the branch in `normalizeClaudePermissionArgs` that checks `options.overrideExisting` for true (dropping the caller-supplied value and injecting the policy mode instead) is never exercised by any current caller. If `overrideExisting: true` is intentionally reserved for future callers, a comment explaining the intended use case would help; otherwise the field can be dropped to simplify the contract.

How can I resolve this? If you propose a fix, please make it concise.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8f58608. Configure here.

): string[] | undefined {
if (!args) {
return args;
return options?.mode ? [CLAUDE_PERMISSION_MODE_ARG, options.mode] : args;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Permission args created without setting-sources for undefined args

Low Severity

When args is undefined and YOLO mode is active, normalizeClaudePermissionArgs now returns ["--permission-mode", "bypassPermissions"] instead of undefined. However, in normalizeClaudeBackendConfig, it's composed after normalizeClaudeSettingSourcesArgs, which already returned undefined for undefined input. This means the resulting args contain --permission-mode bypassPermissions but are missing the security-hardening --setting-sources user flag. Before this PR, both functions preserved undefined consistently.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8f58608. Configure here.

@steipete
steipete merged commit f523bbf into main Apr 23, 2026
74 of 77 checks passed
@steipete
steipete deleted the codex/claude-cli-exec-policy-followup branch April 23, 2026 22:11
@steipete

Copy link
Copy Markdown
Contributor Author

Landed via squash onto main.

  • Local gates: focused Claude/config tests; config/docs/schema/plugin-sdk checks; scoped format; pnpm build; direct Claude non-bypass probe; live Claude CLI backend smoke.
  • CI: GitHub checks green, including install smoke and parity gate.
  • Source commit: 8f58608
  • Landed commit: f523bbf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation extensions: anthropic extensions: cloudflare-ai-gateway extensions: qa-lab gateway Gateway runtime maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant