Follow-up to #2404: interpreter heredoc bodies are re-parsed as shell commands by the iOS Simulator guard
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
**提交人**: zym
**客户端版本**: 0.1.44
---
Related: #2404
## 背景
该问题首次出现在 Cindy 增加内置 iOS Simulator 后。执行普通本地命令时,Codex/Worker 偶发立即结束,界面显示 `aborted by user after 0.1s`。用户没有取消任务;主机日志中的真实原因是:
```text
command execution interrupted by host policy
```
策略提示命令可能绕过内置 iOS Simulator。Lead 与 Orca Worker 均可复现,导致正在进行的文件校验、网页读取和脚本检查直接中断。
## 最小复现
以下命令可正常执行:
```sh
true
python3 -c 'print(1)'
node -e 'console.log(1)'
```
等价的解释器 heredoc 会被立即拒绝:
```sh
python3 - <<'PY'
print(1)
PY
```
Node heredoc 也受影响:
```sh
node - <<'NODE'
console.log(1)
NODE
```
另一个触发方式是:在较大的 shell、解释器或搜索命令中,把 simulator policy 的敏感短语作为普通 `rg`/`grep` 搜索文本,也可能触发同一 guard。
## 源码触发路径
对应用包内 JavaScript 做只读检查后,确认执行链为:
```text
Codex command execution request
→ deps.getShellCommandPolicy(...)
→ getDesktopShellCommandPolicy(command)
→ containsSimulatorBypass(command)
→ denial returned to Codex
→ turn interrupted
→ UI surfaces generic "aborted by user"
```
桌面端策略会先规范化命令,再调用 `containsSimulatorBypass`;命中后返回 `decision: "deny"` 和 iOS Simulator shell denial reason。
`containsSimulatorBypass` 的关键检查链包含:
```text
containsSimulatorBypass
→ containsShellConsumedLiteralBypass
→ containsInterpreterHeredocBypass
```
专用 heredoc 扫描器本身会识别解释器 heredoc、提取 body,并只在 body 包含 simulator executor 时返回 true。这个专用检查结束后,整个多行命令仍继续进入通用 shell segment/token 解析和 unresolved executable expansion 检查。
## 根因判断
heredoc body 被再次按 shell 命令解析。Python body 中普通的 `print(1)` 含括号,通用动态 shell 检查会把这类字符识别为 unresolved executable/dynamic expansion,随后 fail closed,将无害解释器代码判定为可能绕过 Simulator 的命令。
因此触发条件与目标网站、Python/Node 本身及用户取消无关;shell heredoc 的二次解析是稳定的最小触发点。策略敏感文本作为非执行搜索参数时也存在相似的过度分类。
## 期望行为
- 普通 Python/Node heredoc 可以执行。
- 仅当 heredoc body 实际调用 `simctl`、外部 Simulator.app 或等价间接执行器时拒绝。
- 非执行用途的 `rg`/`grep` 搜索字符串不应触发拒绝。
- host policy 拒绝时,界面显示真实策略原因;只有用户实际取消时才显示 `aborted by user`。
## 实际行为
无害 heredoc 被立即拒绝,真实 host-policy denial 被 UI 映射为用户主动中断,造成错误归因,也让上层 Agent 无法针对策略问题选择替代执行方式。
## 复现频率
上述 Python heredoc 最小样例稳定复现;Lead 与 Orca Worker 均出现过同类中断。
## 已尝试与临时规避
目前可通过以下方式规避:
- 使用 `python3 -c` 或 `node -e`。
- 通过文件编辑工具创建临时脚本,再直接运行脚本。
- 暂停使用 shell heredoc。
直接修改已签名应用包会破坏资源签名,并可能影响更新、权限与凭证,因此没有采用。
## 建议修复
1. 专用 heredoc-body simulator 扫描完成后,从通用 shell-command 解析输入中移除已识别的 heredoc body。
2. 或在通用 executable-expansion 检查中跳过属于解释器 heredoc body 的行。
3. 保留 `containsInterpreterHeredocBypass()`,继续拦截 body 内真实的 `simctl`、Simulator.app 启动和编码/间接执行。
4. 将 host-policy denial reason 原样传递给上层 UI,避免统一显示为用户取消。
## 建议回归用例
应允许:
```sh
python3 - <<'PY'
print(1)
PY
```
```sh
node - <<'NODE'
console.log(1)
NODE
```
```sh
python3 - <<'PY'
import urllib.request
print(len(urllib.request.urlopen("https://example.com").read()))
PY
```
应拒绝:
- heredoc body 调用 `xcrun simctl`。
- heredoc body 启动外部 Simulator.app。
- 编码或间接命令最终解析到上述执行器。
- 外部 simulator 生命周期和 mutation 自动化。
还应覆盖:把 simulator 相关源码文本作为 `rg`/`grep` 的非执行搜索参数时允许执行。
---
**版本区域**: CN
**OS**: darwin arm64 (25.5.0)
**界面语言**: zh-CN
Contributor guide
Research direction
Start at deps.getShellCommandPolicy and follow getDesktopShellCommandPolicy into containsSimulatorBypass, containsShellConsumedLiteralBypass, and containsInterpreterHeredocBypass. Reproduce the Python and Node heredoc examples, then verify that harmless bodies and non-executing rg/grep search text are allowed while simulator executors remain denied and the real host-policy reason reaches the UI.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, python, typescript
- Domain
- desktop, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 54/100