`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 項全過。
43 lines
1.8 KiB
JavaScript
43 lines
1.8 KiB
JavaScript
#!/usr/bin/env node
|
||
// PreToolUse guard:人格鎖驗證 + 跨人格資料隔離。
|
||
//
|
||
// 定位:**防漂移的護欄,不是對抗性沙箱**。它擋的是「模型在正常工作中不小心讀到、
|
||
// 寫到別的人格」——對模型會自然寫出來的形式一律 deny;它不擋一個知道 guard 存在、
|
||
// 刻意要繞過去的對手(直譯器逃逸、逐段 cd、引號切割 token 都繞得過,這條路用正則
|
||
// 補不完)。詳見 README 的〈guard 擋得住什麼、擋不住什麼〉。
|
||
//
|
||
// 擋下的情形:
|
||
// * 讀寫非「本 session 當前人格」的人格目錄(Read/Write/Edit/LS 的路徑欄位、
|
||
// Glob/Grep 的 path 與樣式欄位、沒給 path 時的 cwd、Bash 指令裡直接出現的路徑)
|
||
// * guest(persona-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}`,
|
||
},
|
||
});
|