소스 검색

fix(physics67): prevent agent escape to repo root + gitignore defense

审计发现 (用户提问 "项目有些文件夹代码是智能体运行非法写入的请审计"):

  03_analysis/hermitian_review_section.md       — 写到 repo 根 (应在 workspace)
  nh_qgt/cycle_1/00_plan/research_plan.md       — 同上
  nh_qgt/cycle_1/models/nrssh.py                — 同上

physics67 跑了两次, 都从 workspace 边界逃逸到了 main 仓库根。
内容是真实物理研究 (NR-SSH / Hatano-Nelson / 非厄米 QGT) — 已外部备份后
本 commit 之前 `rm -rf 03_analysis/ nh_qgt/` 清除。

## 根因 1 — orchestrator.yml base_workspace fallback 没强制绝对路径

  line 56 原文 (instruction-style):
    "如果用户未给出, 使用当前项目下 `physics_runs/{安全化标题}`"

  问题: relative path + cwd=repo_root → 直接落在 repo 主仓库。
  实测: 模型连 `physics_runs/` 前缀都没用 (生成的 base_workspace =
  "nh_qgt"), `cycle_1` 拼上去就是 `./nh_qgt/cycle_1`。

  Fix: 明确要求"绝对路径以 / 开头"为铁律, 并指示当用户未给 base_workspace
  时, 必须先调用 Bash 工具 `echo "$HOME/physics_runs/{title}"` 拿到绝对
  路径再用。加 self-check 段: 调用任何 sub-agent 前自检 input.workspace
  开头是否 `/`。

## 根因 2 — sub-agent 用 `cd {workspace} && python3 STAGE/x.py`

  analyst.yml:39, simulator.yml:36 原文:
    `cd {workspace} && python3 03_analysis/analyze_results.py`
    `cd {workspace} && python3 02_sim/simulate_main.py`

  问题: 当 {workspace} substitution 失败 (从 fromconfig 传过来的 input
  没替换), `cd ` (空参) 在 macOS bash 里跑到 $HOME, 然后相对路径
  `03_analysis/` 写到 $HOME 而不是 workspace。这次还更糟: 跑到了 cwd
  = repo 根。

  Fix: 全部改成绝对路径调用 `python3 {workspace}/STAGE/x.py`, 脚本内部
  如需相对 IO 在第一行 `os.chdir("{workspace}")`。`writer.yml` 的
  cd 仍保留 (LaTeX 编译需要 cwd), 但加 `test -d "{workspace}/04_paper"
  || exit 1` 守门。

## 防御 — .gitignore

  即使未来 LLM 又生成 escape 路径, 这些 pattern 拦下 repo 根的:
    /physics_runs/      # legitimate fallback
    /cycle_[0-9]*/      # base_workspace/cycle_N rooted at /
    /_gates/            # gate 输出
    /0[0-9]_{plan,lit,sim,analysis,paper,review}/   # stage 子目录

  实测确认 `git check-ignore` 命中。注意: pattern 必须独占行, 不能写
  inline comment (`/foo/ # comment` 会把整串当成 pattern) — 之前犯过
  这个错。

## 不在本 commit 范围

- agentexample/physics67/agents/{reviewer,idea-planner,artifact-gate}.yml 也用了
  `{workspace}/` 路径但没 cd-relative 模式, 不需要改。
- {workspace} substitution 本身在 fromconfig 端的失败模式 (为什么有时候
  没替换) — 是 lambdagent 侧的真实 bug, 单独跟踪。
- audit 报告里其它 finding 不在本 commit 范围。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kenny67nju 3 달 전
부모
커밋
c764d1193e

+ 24 - 0
.gitignore

@@ -202,6 +202,30 @@ webui/.vite/
 agentexample/**/wiki/
 agentexample/**/workspace/
 
+# ==============================
+# physics67 escape defense — agent-written dirs that leaked to repo root.
+# Historical bug (see docs/AUDIT_2026-06-05.md "项目外目录非法写入" 一节):
+# if `{workspace}` substitution failed inside sub-agent prompts,
+# `cd {workspace} && python3 03_analysis/x.py` became `cd && python3
+# 03_analysis/x.py`, writing 03_analysis/ to cwd (= repo root). Prompts
+# now require absolute paths; these rules are belt-and-suspenders so an
+# LLM regression won't silently pollute main.
+# ==============================
+# orchestrator.yml default fallback dir:
+/physics_runs/
+# `{base_workspace}/cycle_N` accidentally rooted at /:
+/cycle_[0-9]*/
+# artifact-gate dir:
+/_gates/
+# Per-stage agent output dirs (analyst writes 03_analysis/, writer writes
+# 04_paper/, etc. — only catch when they leak to root, not inside workspace):
+/0[0-9]_plan/
+/0[0-9]_lit/
+/0[0-9]_sim/
+/0[0-9]_analysis/
+/0[0-9]_paper/
+/0[0-9]_review/
+
 # ==============================
 # Knowledge base index files (generated, can be large)
 # BM25 index, vector embeddings (numpy), metadata pickles

+ 8 - 1
agentexample/physics67/agents/analyst.yml

@@ -36,7 +36,14 @@ systemPrompt: |
      只写 `analysis_results.json`,其中 status="fail",并在
      `analysis_report.md` 中列出模拟失败原因,然后 terminate。
   4. 写入 `{workspace}/03_analysis/analyze_results.py`,用它读取 02_sim 中的数据文件。
-  5. 执行:`cd {workspace} && python3 03_analysis/analyze_results.py`。
+  5. 执行 (用绝对路径, 禁止 `cd`):
+     `python3 {workspace}/03_analysis/analyze_results.py`
+     脚本内部如需相对 IO, 第一行必须 `os.chdir("{workspace}")`,确保后续
+     `02_sim/...` 等相对路径都在 workspace 内。
+     **禁止**用 `cd {workspace} && python3 03_analysis/...` 这种 cd-relative
+     模式 — 当 {workspace} substitution 失败时, cd 会变成 `cd ` (空参), 把
+     `03_analysis/` 写到 repo 根。审计参见 docs/AUDIT_2026-06-05.md "非法
+     写入"一节。
   6. 若失败,读取错误、修复脚本并重试,最多 3 次。
   7. 写入:
      - `{workspace}/03_analysis/analysis_results.json`

+ 6 - 1
agentexample/physics67/agents/simulator.yml

@@ -33,7 +33,12 @@ systemPrompt: |
   2. 创建 `{workspace}/02_sim/`。
   3. 根据 idea_plan.json 中的 `model`、`observables`、`experiments` 生成适配脚本。
   4. 写入 `{workspace}/02_sim/simulate_main.py`。
-  5. 执行:`cd {workspace} && python3 02_sim/simulate_main.py`。
+  5. 执行 (用绝对路径, 禁止 `cd`):
+     `python3 {workspace}/02_sim/simulate_main.py`
+     脚本如需相对 IO, 第一行 `os.chdir("{workspace}")`。**禁止**用
+     `cd {workspace} && python3 02_sim/...` — 当 {workspace} substitution
+     失败时 cd 变 `cd ` (空参), `02_sim/` 落到 repo 根。审计参见
+     docs/AUDIT_2026-06-05.md "非法写入"一节。
   6. 若失败,读取错误、修复脚本并重试,最多 3 次。不要在未成功运行时声称完成。
   7. 写入:
      - `{workspace}/02_sim/sim_results.json`

+ 3 - 1
agentexample/physics67/agents/writer.yml

@@ -115,7 +115,9 @@ systemPrompt: |
 
   ## 编译命令建议
   ```bash
-  cd {workspace}/04_paper
+  # 第一行先校验路径绝对, 否则任何 latex 输出都可能掉到 cwd (repo 根):
+  test -d "{workspace}/04_paper" || { echo "FATAL: workspace path not absolute or missing"; exit 1; }
+  cd "{workspace}/04_paper"
   export PATH=/Library/TeX/texbin:$PATH
   latexmk -pdf -interaction=nonstopmode -halt-on-error paper.tex
   # fallback:

+ 16 - 2
agentexample/physics67/orchestrator.yml

@@ -53,11 +53,25 @@ systemPrompt: |
   analysis gate fail → call_physanalyst;paper gate fail → call_physwriter。
 
   ## 轮次和工作区
-  - base_workspace: 用户指定的绝对路径;如果用户未给出,使用当前项目下 `physics_runs/{安全化标题}`。
+
+  **铁律:所有 workspace 路径必须是绝对路径 (以 `/` 开头)。** 这条规则是
+  为了防止 sub-agent 把数据/图/论文写到 repo 根 — 已经发生过两次, 见
+  docs/AUDIT_2026-06-05.md "项目外目录非法写入" 一节。
+
+  - base_workspace:
+    * 用户指定的绝对路径 → 直接用。
+    * 用户未给出 → 你必须先调用 Bash 工具:
+        `echo "$HOME/physics_runs/{安全化标题}"`
+      拿到的绝对路径作为 base_workspace。**禁止**使用 `physics_runs/...`
+      这种相对形式 — relative + cwd=repo_root 会污染主仓库。
   - cycle: 当前轮次,取 1、2、3。
-  - workspace: `{base_workspace}/cycle_{cycle}`。
+  - workspace: `{base_workspace}/cycle_{cycle}` (绝对路径)
   - prev_workspace: cycle=1 时为空字符串;cycle>1 时为 `{base_workspace}/cycle_{cycle-1}`。
 
+  **校验**: 调用任何 sub-agent 前, 自己检查 input.workspace 是否以 `/`
+  开头。如果不是, 不要发出工具调用 — 重新算 base_workspace 直到拿到
+  绝对路径。
+
   每次调用 sub-agent 时都必须传入:
   {
     "research_question": "...",