博客 • 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 怎樣分支。

先記住這一點: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 前綴檢查。

settings:permissions.blockWritesOutsideWorkingDirectories 爲 true。
{
  "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。

會話 context:workingDirectories 與 blockOutside。
{
  "workingDirectories": ["/home/dev/myapp"],
  "blockOutside": true
}

workingDirectories 至少一項且每項爲非空字符串;空數組會觸發 no_workdirs。路徑比對前應對根目錄去掉尾部斜槓,再判斷 path 等於根或以其加斜槓爲前綴。

會話 context JSON Schema:先過形狀再進 write_path_gate。
{
  "$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 規則對不上。

允許:Write 指向 workingDirectory 內文件。
{
  "tool": "Write",
  "path": "/home/dev/myapp/src/main.ts"
}
拒絕:Edit 指向 workingDirectory 外路徑。
{
  "tool": "Edit",
  "path": "/etc/hosts"
}

拒絕記錄裏 decision 與 reason 應怎樣寫?

當 path 不在任一 workingDirectory 前綴下,客戶端不應執行寫入,並在 permission 或 tool 日誌 JSON 裏寫 decision denied 與 reason outside_working_directories,同時保留 tool 與 path 原值。NotebookEdit 與 Edit 共用同一 reason 枚舉,便於報表一條規則覆蓋三種寫入 tool。

拒絕日誌:decision denied,reason outside_working_directories。
{
  "tool": "Edit",
  "path": "/etc/hosts",
  "decision": "denied",
  "reason": "outside_working_directories"
}
日誌或調用現象write_path_gate 返回運維動作
path 在 workingDirectories 前綴內,tool 爲 Write 或 NotebookEditallow正常放行,日誌無需 outside_working_directories。
path 在根外,tool 爲 Edit,blockOutside 爲 truedeny_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 拒絕。

write_path_gate:schema、not_enabled、missing_path、not_write_tool、no_workdirs、allow、deny_outside。
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. 1
    用 Schema 固定 context 形狀

    打開 JSON Schema。第一欄貼會話 context schema,第二欄貼 workingDirectories 與 blockOutside 樣本。required 或 minItems 錯誤應先標紅,write_path_gate 不應在形狀錯誤時返回 allow 或 deny_outside。

  2. 2
    用格式化覈對 tool 調用

    打開 JSON 格式化,貼 Write、Edit 或 NotebookEdit 調用 JSON。確認 tool 與 path 鍵存在且 path 爲字符串,再對照 write_path_gate 是否應走 missing_path。

  3. 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 返回值。

返回博客