博客 • Claude Code / 權限
升級到 Claude Code 2.1.314 之後,開啓 permissions.blockWritesOutsideWorkingDirectories 時,Write 和 Edit 的路徑要在 JSON 日誌裏怎樣記下拒絕?
2026 年 10 月 30 日,Anthropic 發佈 Claude Code 2.1.314。新增設置 permissions.blockWritesOutsideWorkingDirectories:當其爲 true 時,Write、Edit、NotebookEdit 在 default、acceptEdits、bypassPermissions 等任意 permission 模式下,只要目標 path 落在 workingDirectories 之外,客戶端都會直接拒絕執行。
這與 permission policy 裏的 allow 或 deny 規則不是同一層:後者仍管工具種類與交互式審批,而工作目錄外寫入是在 tool 執行前按 path 前綴硬擋。審計 JSON 應同時保留 settings 開關、會話 workingDirectories、tool 與 path 字段,以及 decision denied 與 reason outside_working_directories,便於與本地 write_path_gate 對照。
allow 與 deny 隊列寫法見 Agent permission policy;Managed Agents 的 auto 評測上下文見 Managed Agents auto 評測;會話級工作流 JSON 見 Claude Code workflow。本篇只寫 2.1.314 的工作目錄外寫入攔截、日誌字段形狀,以及 write_path_gate 怎樣分支。
- settings 裏 permissions.blockWritesOutsideWorkingDirectories 爲 true 時,會話必須提供非空 workingDirectories,否則 write_path_gate 應返回 no_workdirs。
- Write、Edit、NotebookEdit 的調用 JSON 必須帶字符串 path;缺失或非字符串時閘門返回 missing_path,不應誤記爲 outside_working_directories。
- path 落在任一 workingDirectory 或其子路徑下時返回 allow;落在根外例如 /etc/hosts 時客戶端拒絕並在日誌寫 decision denied 與 reason outside_working_directories。
- Bash、Read 等非寫入類 tool 不在攔截集合內;write_path_gate 對它們返回 not_write_tool,與 deny_outside 分開計數。
先記住這一點:2.1.314 改的是工作目錄邊界上的寫入硬擋與對應日誌字段,不是重寫 permission policy 裏的 allow 或 deny 表。下面按 changelog、settings JSON、會話 workingDirectories、Write Edit NotebookEdit 調用樣本、拒絕日誌與對照表、write_path_gate 源碼,以及 JSONNote 裏怎樣校驗形狀。
2.1.314 怎樣把工作目錄外的 Write、Edit、NotebookEdit 一律擋掉?
變更說明見 Claude Code changelog,標籤頁見 v2.1.314 release。相對更早版本,最顯眼的是 permissions.blockWritesOutsideWorkingDirectories:開啓後寫入類 tool 不再因爲 bypassPermissions 或 acceptEdits 而落到 /etc 一類路徑上。
攔截髮生在 path 解析之後、文件寫入之前。日誌裏應保留原始 tool 名與絕對 path,方便 SIEM 按 reason outside_working_directories 聚合,而不是隻記一條泛化的 permission denied。
permissions.blockWritesOutsideWorkingDirectories 在 settings JSON 裏怎樣寫?
在用戶或託管 settings 文件的 permissions 對象里加入 blockWritesOutsideWorkingDirectories 並設爲 true。該開關與 permission mode 正交:即使會話處於 bypassPermissions,寫入類 tool 仍要過 workingDirectories 前綴檢查。
{
"permissions": {
"blockWritesOutsideWorkingDirectories": true
}
}
若運維把開關寫成字符串 true 或漏掉 permissions 層級,本地 schema 校驗應先失敗並返回 schema,再進入 write_path_gate 的業務分支。不要把形狀錯誤當成 not_enabled。
會話上下文裏 workingDirectories 與 blockOutside 怎樣綁定?
客戶端在會話上下文 JSON 裏寫入 workingDirectories 數組與 blockOutside 布爾值。blockOutside 爲 true 且 settings 開關已開時,才進入前綴比對;爲 false 時 write_path_gate 直接返回 not_enabled,即使 settings 裏寫了 true。
{
"workingDirectories": ["/home/dev/myapp"],
"blockOutside": true
}
workingDirectories 至少一項且每項爲非空字符串;空數組會觸發 no_workdirs。路徑比對前應對根目錄去掉尾部斜槓,再判斷 path 等於根或以其加斜槓爲前綴。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["workingDirectories", "blockOutside"],
"properties": {
"workingDirectories": {
"type": "array",
"minItems": 1,
"items": { "type": "string", "minLength": 1 }
},
"blockOutside": { "type": "boolean" }
}
}
Write、Edit、NotebookEdit 的 tool 調用 JSON 要帶哪些字段?
審計樣本至少包含 tool 與 path。tool 取值 Write、Edit 或 NotebookEdit 時進入寫入攔截邏輯;path 應爲絕對路徑字符串。相對路徑若被客戶端解析,日誌仍建議寫解析後的絕對值,避免 SIEM 規則對不上。
{
"tool": "Write",
"path": "/home/dev/myapp/src/main.ts"
}
{
"tool": "Edit",
"path": "/etc/hosts"
}
拒絕記錄裏 decision 與 reason 應怎樣寫?
當 path 不在任一 workingDirectory 前綴下,客戶端不應執行寫入,並在 permission 或 tool 日誌 JSON 裏寫 decision denied 與 reason outside_working_directories,同時保留 tool 與 path 原值。NotebookEdit 與 Edit 共用同一 reason 枚舉,便於報表一條規則覆蓋三種寫入 tool。
{
"tool": "Edit",
"path": "/etc/hosts",
"decision": "denied",
"reason": "outside_working_directories"
}
| 日誌或調用現象 | write_path_gate 返回 | 運維動作 |
|---|---|---|
| path 在 workingDirectories 前綴內,tool 爲 Write 或 NotebookEdit | allow | 正常放行,日誌無需 outside_working_directories。 |
| path 在根外,tool 爲 Edit,blockOutside 爲 true | deny_outside | 對齊客戶端日誌字段 decision denied 與 reason outside_working_directories。 |
| blockOutside 爲 false 或 settings 開關未生效 | not_enabled | 不要誤報 outside_working_directories;先確認 settings 與會話 blockOutside。 |
| tool 爲 Bash,path 指向任意位置 | not_write_tool | 與工作目錄寫入攔截分開統計;Bash 仍受其它 permission 規則約束。 |
write_path_gate 怎樣在 schema 與七條業務分支間選擇?
實現順序:先用會話 context JSON Schema 校驗 workingDirectories 與 blockOutside;blockOutside 爲 false 返回 not_enabled;讀取 call 的 path,非字符串返回 missing_path;tool 不在 Write、Edit、NotebookEdit 集合返回 not_write_tool;workingDirectories 爲空返回 no_workdirs;path 匹配任一前綴返回 allow;否則 deny_outside,對應日誌 reason outside_working_directories。
不要把 deny_outside 與 missing_path 合成單一 blocked 位。報表分開計數,才能區分路徑填錯、工具類型不對、以及真正的 outside_working_directories 拒絕。
def actual_type(data: object) -> str:
if data is None:
return "null"
if isinstance(data, bool):
return "boolean"
if isinstance(data, list):
return "array"
if isinstance(data, int):
return "integer"
if isinstance(data, float):
return "number"
if isinstance(data, str):
return "string"
if isinstance(data, dict):
return "object"
raise TypeError(type(data))
def validate(data: object, schema: dict) -> list[str]:
errors: list[str] = []
if not isinstance(schema, dict):
return errors
types = schema.get("type")
if isinstance(types, str):
types = [types]
actual = actual_type(data)
if types and not any(t == actual or (t == "number" and actual == "integer") for t in types):
errors.append("type")
if "enum" in schema and not any(value == data for value in schema["enum"]):
errors.append("enum")
if isinstance(data, str) and "minLength" in schema and len(data) < schema["minLength"]:
errors.append("minLength")
if isinstance(data, dict) and schema.get("properties"):
for key in schema.get("required") or []:
if key not in data:
errors.append(f"required:{key}")
if schema.get("additionalProperties") is False:
for key in data:
if key not in schema["properties"]:
errors.append(f"additional:{key}")
for key, sub in schema["properties"].items():
if key in data and data[key] is not None:
errors.extend(validate(data[key], sub))
return errors
def write_path_gate(ctx: dict, call: dict, schema: dict) -> str:
if validate(ctx, schema):
return "schema"
if not ctx.get("blockOutside"):
return "not_enabled"
path = call.get("path")
if not isinstance(path, str):
return "missing_path"
tool = call.get("tool")
if tool not in {"Write", "Edit", "NotebookEdit"}:
return "not_write_tool"
roots = ctx.get("workingDirectories") or []
if not roots:
return "no_workdirs"
for root in roots:
base = root.rstrip("/")
if path == base or path.startswith(base + "/"):
return "allow"
return "deny_outside"
在 JSONNote 裏怎樣校驗 settings、context 與拒絕日誌?
- 1用 Schema 固定 context 形狀
打開 JSON Schema。第一欄貼會話 context schema,第二欄貼 workingDirectories 與 blockOutside 樣本。required 或 minItems 錯誤應先標紅,write_path_gate 不應在形狀錯誤時返回 allow 或 deny_outside。
- 2用格式化覈對 tool 調用
打開 JSON 格式化,貼 Write、Edit 或 NotebookEdit 調用 JSON。確認 tool 與 path 鍵存在且 path 爲字符串,再對照 write_path_gate 是否應走 missing_path。
- 3用 Diff 對比允許與拒絕日誌
打開 JSON Diff。左邊放 workingDirectory 內的允許樣本,右邊放帶 decision denied 與 reason outside_working_directories 的拒絕樣本。Diff 高亮的 reason 字段就是 SIEM 規則最該鎖定的一行。
常見問題
bypassPermissions 模式還能寫到工作目錄外嗎?
不能。2.1.314 起 blockWritesOutsideWorkingDirectories 爲 true 時,Write、Edit、NotebookEdit 在 bypassPermissions 下仍會被 path 前綴擋在工作目錄外,日誌仍應寫 reason outside_working_directories,而不是假設 bypass 跳過目錄檢查。
符號鏈接會不會繞過 workingDirectories 前綴?
客戶端按請求裏的 path 字符串做前綴比對,不在這裏展開 symlink 目標路徑。運維應把 workingDirectories 設成真實項目根,並在審計裏記錄 agent 提交的 path;本篇只說明校驗邏輯,不提供繞過或攻擊步驟。
這與 permission policy 裏的 allow 或 deny 規則衝突嗎?
不衝突,而是不同層。permission policy 決定某類 tool 默認允許還是要走審批隊列;blockWritesOutsideWorkingDirectories 則在已允許執行寫入類 tool 時仍按 path 硬擋目錄外目標。兩套 JSON 應分開保存,write_path_gate 只鏡像目錄層,不替 policy 做 allow 或 deny 判定。
Read 或 Grep 讀工作目錄外文件會被記成 outside_working_directories 嗎?
不會。outside_working_directories 只對應 Write、Edit、NotebookEdit 的寫入拒絕。Read 類 tool 走其它 permission 與 sandbox 規則;本地 write_path_gate 對它們應返回 not_write_tool,不要把讀操作混進寫入拒絕指標。
no_workdirs 與 deny_outside 在日誌裏怎樣區分?
no_workdirs 是本地閘門在 workingDirectories 缺失或爲空時的配置錯誤分支,通常不應僞造 outside_working_directories reason。deny_outside 纔對應客戶端已拒絕的具體 path。報表應分別計數 schema、no_workdirs、deny_outside,避免把未配置根目錄誤當成外泄寫入。
總結
Claude Code 2.1.314 用 permissions.blockWritesOutsideWorkingDirectories 把 Write、Edit、NotebookEdit 鎖在工作目錄前綴內,並在拒絕時把 decision denied 與 reason outside_working_directories 寫進 JSON 日誌。運維應用 settings、會話 context、tool 調用與日誌四套樣本驅動 write_path_gate,而不是隻盯 permission mode 名稱。
直接結論:context 不過 schema 返回 schema;blockOutside 爲 false 返回 not_enabled;path 非字符串返回 missing_path;tool 不是 Write、Edit、NotebookEdit 返回 not_write_tool;workingDirectories 爲空返回 no_workdirs;path 落在前綴內返回 allow;否則返回 deny_outside,日誌對齊 decision denied 與 reason outside_working_directories。
下一步:用 Schema 固定 context 與 settings,用格式化看清 tool 與 path,用 Diff 對比允許與拒絕日誌,並在報表裏分開統計 write_path_gate 返回值。