# CodeWhale: rlm_eval auto-approves arbitrary Python execution, bypassing the user's approval policy (RCE)

CodeWhale's rlm_eval tool returns ApprovalRequirement::Auto, so model-supplied Python runs unsandboxed on the host without ever consulting the user's --approval-policy.

- Payout: Research
- Severity: high
- Program: CodeWhale
- Found by: sai-sh
- Disclosed: 2026-09-04
- CVE: CVE-2026-75858
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-wrj3-vj8c-784f
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-wrj3-vj8c-784f
- Tags: area:ai domain:ai domain:desktop impact:rce kind:research platform:ghsa sev:high tech:llm tech:python tech:rust technique:incomplete-fix technique:source-review vuln:auth-bypass vuln:prompt-injection vuln:rce

**Target:** rlm_eval / rlm_open agent tools in the CodeWhale TUI (Rust)
**Root cause:** rlm_eval overrides the trait default (Required for ExecutesCode tools) with ApprovalRequirement::Auto; the engine's gate ANDs `approval_requirement() != Auto` with `!auto_approve`, so Auto short-circuits the user's --approval-policy entirely.
**Steps:**
1. Note rlm_eval::approval_requirement() returns `ApprovalRequirement::Auto` (crates/tui/src/tools/rlm.rs), overriding the spec.rs default of Required for ExecutesCode.
2. Point a provider's `base_url` at a local mock returning canned `tool_calls`.
3. Have the agent call `rlm_open` then `rlm_eval` with code such as `import os, getpass, socket; open('/tmp/pwned_by_rlm_eval','w').write(getpass.getuser()+'@'+socket.gethostname()+':'+os.getcwd())`.
4. Run the interactive TUI under `--approval-policy on-request` (no `--auto`, no `--yolo`); no approval dialog appears and the sentinel file is written.
**How found:** Compared rlm_eval against the already-patched sibling run_tests (CVE-2026-45311) and noticed the fix never reached rlm_eval/rlm_open; traced approval_requirement() into the engine gate at engine.rs:845 to confirm Auto makes approval_required false.
**Exploitation:** Prompt injection in any content the agent reads (web page, fetched URL, repo file, MCP tool result) induces a single rlm_eval call; rlm_open can stage a content string, file read, or URL fetch into the same Python kernel first. The code runs in a real python3 at the user's UID with no sandbox, letting the attacker read SSH keys, cloud credentials and ~/.codewhale/auth.json, or write shell rc files/authorized_keys for persistence.
**Escalation:** rlm_open stages attacker-controlled content/file/URL into the kernel before rlm_eval executes against it, widening the injection surface beyond a single string.
**Insight:** When a code-execution tool is patched for missing approval, grep every sibling tool for its own approval_requirement() override — a per-tool `Auto` silently defeats a global --approval-policy because the gate ANDs the two conditions.

## Proof of concept (copied exactly from the original report)

**Python payload writing sentinel file**

```
import os, getpass, socket
open('/tmp/pwned_by_rlm_eval','w').write(getpass.getuser()+'@'+socket.gethostname()+':'+os.getcwd())
```

