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 項全過。
This commit is contained in:
+22
-6
@@ -2717,6 +2717,14 @@ const PATH_TOOL_FIELDS = {
|
||||
Grep: ["path"],
|
||||
LS: ["path"],
|
||||
};
|
||||
// 樣式欄位本身就會帶路徑:`Glob { pattern: "<home>/*/IDENTITY.md" }` 不給 `path` 也掃得到
|
||||
// 別人的身分檔,這是模型最自然會寫出來的列舉方式。它是相對於 `path`(沒給就相對 cwd)
|
||||
// 解析的,所以基準點跟 PATH_TOOL_FIELDS 不同,另外列一張表。
|
||||
// 注意 `Grep.pattern` 是正規表示式、不是路徑,故意不收。
|
||||
const PATTERN_TOOL_FIELDS = {
|
||||
Glob: ["pattern"],
|
||||
Grep: ["glob"],
|
||||
};
|
||||
|
||||
export const GUEST_SAFE_SUBCOMMANDS = new Set([
|
||||
"show", "status", "list", "recall", "room", "remember", "leave", "brief", "think", "said",
|
||||
@@ -2779,12 +2787,20 @@ export function personaSlugOf(target) {
|
||||
|
||||
export function extractPaths(toolName, toolInput, cwd) {
|
||||
const out = [];
|
||||
for (const field of PATH_TOOL_FIELDS[toolName] || []) {
|
||||
const value = toolInput?.[field];
|
||||
if (typeof value === "string" && value) {
|
||||
const resolved = resolvePath(value, cwd);
|
||||
if (resolved) out.push(resolved);
|
||||
}
|
||||
const push = (value, base) => {
|
||||
if (typeof value !== "string" || !value) return;
|
||||
const resolved = resolvePath(value, base);
|
||||
if (resolved) out.push(resolved);
|
||||
};
|
||||
for (const field of PATH_TOOL_FIELDS[toolName] || []) push(toolInput?.[field], cwd);
|
||||
if (PATTERN_TOOL_FIELDS[toolName]) {
|
||||
const rawPath = typeof toolInput?.path === "string" ? toolInput.path : "";
|
||||
// 樣式相對於 `path`;`path` 沒給的話,搜尋起點就是 hook event 的 cwd
|
||||
const base = rawPath ? resolvePath(rawPath, cwd) || cwd : cwd;
|
||||
for (const field of PATTERN_TOOL_FIELDS[toolName]) push(toolInput?.[field], base);
|
||||
// Grep/Glob 不給 `path` 時就是「掃 cwd」。少了這一條,cwd 站在人格倉庫底下的
|
||||
// Grep 會解析出空陣列 → guard 不表態 → 整批人格資料直接穿過去。
|
||||
if (!rawPath) push(cwd, cwd);
|
||||
}
|
||||
if (toolName === "Bash") {
|
||||
const command = toolInput?.command || "";
|
||||
|
||||
Reference in New Issue
Block a user