Files
jiantw83 7549c0b69f fix(guard): 補上 Glob/Grep 的兩個自然破口,並誠實標示 guard 的定位(S2)
`extractPaths()` 只解析「含 / 且展開後包含 home 或 personas」的 token,
於是兩種**模型最自然會寫出來的列舉方式**整路穿過去:

1. `PATH_TOOL_FIELDS.Glob` 只列 `path`,所以樣式欄位不被檢查——
   `Glob { pattern: "<home>/*/IDENTITY.md" }`(不給 `path`)可以掃出全部人格的身分檔。
   新增 `PATTERN_TOOL_FIELDS`(`Glob.pattern`、`Grep.glob`),且**相對於 `path` 解析**
   (沒給 `path` 才相對 cwd),因為樣式的基準點跟路徑欄位不同。
   `Grep.pattern` 是正規表示式、不是路徑,故意不收,免得誤攔 `a/b/c` 這種樣式。
2. `Grep`/`Glob` 沒給 `path` 時 `extractPaths` 回空陣列 → guard 不表態,
   於是 cwd 站在 `~/.claude/personas` 或別人的人格底下直接 `Grep` 就整批穿過去。
   改成把 hook event 的 `cwd` 當預設目標。

其餘的直譯器逃逸(`node -e`、`python3 -c`、逐段 `cd`、引號切割 token)**不用正則補**:
Bash 圖靈完備,追指令字串永遠落後一步,每加一條正則就多一批誤攔正常指令的風險。
改為把文件的措辭修正成誠實的定位——

- README 新增〈guard 擋得住什麼、擋不住什麼〉:明說 guard 是**防漂移的護欄,
  對正常寫法一律 deny,不是對抗性沙箱**,並逐條列出擋得住與擋不住的形式,
  以及「真要對抗性隔離請走 OS 層」。
- 拿掉會誤導的字眼:`PreToolUse` 那列的「唯一強制點」、guard.mjs 檔頭的
  「唯一的強制執行點」、「不能被繞過的關鍵」。

測試:新增 13 項——樣式欄位指向全倉庫/別的人格/自己、樣式相對 path 解析、
`Grep.pattern` 不誤判成路徑、不給 path 時 cwd 在別人格/倉庫根/自己人格/
普通專案的四種情形、給了 path 就不看 cwd,以及兩項文件措辭的迴歸檢查。
355 → 368 項全過。
2026-07-31 09:23:28 +00:00

43 lines
1.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// PreToolUse guard:人格鎖驗證 + 跨人格資料隔離。
//
// 定位:**防漂移的護欄,不是對抗性沙箱**。它擋的是「模型在正常工作中不小心讀到、
// 寫到別的人格」——對模型會自然寫出來的形式一律 deny;它不擋一個知道 guard 存在、
// 刻意要繞過去的對手(直譯器逃逸、逐段 cd、引號切割 token 都繞得過,這條路用正則
// 補不完)。詳見 README 的〈guard 擋得住什麼、擋不住什麼〉。
//
// 擋下的情形:
// * 讀寫非「本 session 當前人格」的人格目錄(Read/Write/Edit/LS 的路徑欄位、
// Glob/Grep 的 path 與樣式欄位、沒給 path 時的 cwd、Bash 指令裡直接出現的路徑)
// * guestpersona-guest sub agent)寫入任何人格檔案,或換讀別的人格
// * CLI 帶假的 --session(冒用其他程序身分)/主程序冒用 guest 或 sleeper 身分
// * 目標人格的鎖屬於其他還活著的程序
//
// 身分類的判定 CLI 自己也會再驗一次,不把最後一道關卡放在 hook 上——
// hook 認得出這支 CLI 靠的是檔名,改個名字就整路不表態了。
import { readEvent, respond } from "./_hook.mjs";
import * as pl from "../scripts/persona-lib.mjs";
const event = readEvent();
if (!event) process.exit(0);
let result;
try {
result = pl.guardDecide(event);
} catch (err) {
// guard 自己壞掉不該擋住整個 session
process.stderr.write(`persona guard 內部錯誤:${err.message}\n`);
process.exit(0);
}
if (result.decision !== "deny") process.exit(0);
respond({
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: `[jsc-persona 隔離] ${result.reason}`,
},
});