makecindy / makecindy/cindy

Follow-up to #2404: interpreter heredoc bodies are re-parsed as shell commands by the iOS Simulator guard

Open
#2,420 1 comment 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.