permissions.deny 裡寫的規則,有時候不會像寫的那樣生效。

在 2026-09-11 到 09-24 的 2 週內,Claude Code 權限相關修正總共出了 7 個版本
(2.1.269 / 271 / 273 / 274 / 275 / 280 / 282)。
其中 3 個我已經自己測過並寫成文章。這次,把剩下的也一起測完整理成一篇。

就算是最新的 Claude Code 2.1.282,仍然存在能繞過的情況。

在最新版(2.1.282)中,拒絕 Read(./secret.txt) 的狀態

column -t secret.txt          會停下
grep -v NOTHING ./sec*.txt    內容被回傳了(2/2)

切換成 bypass 模式後
被 eval / env -C 包起來的寫入  2/2 仍可繞過

驗證環境:Windows 10 / 獨立安裝 Claude Code 2.1.277・2.1.281・2.1.282 / claude -p /
測量時間為 2026-09-25(過去各回以各自文章日期為準)


目次與結論

#調查方式已修正版本最新版中的狀態1tee 寫入被拒絕的檔案2.1.269停止2eval / env -C 包起來(bypass 時)—仍可繞過3寫成 Write(...) 的拒絕規則—不生效(要用 Edit(...))4不受信任資料夾中的 settings.json—整個被忽略5用 grep 的萬用字元讀檔—內容會回來6rm -rf $(pwd)2.1.2802.1.277 中真的刪掉了7無法解析的那一行本身—會停下(這和拒絕規則是不同機制)有 4 項仍屬於「還沒修好」的列。 以下逐項說明我測到的內容。


共通的測量方法

在一個丟棄用目錄裡只放拒絕規則,只看檔案內容有沒有被改掉/有沒有被回傳。
不使用自我申報。

{
  "permissions": {
    "deny": ["Read(./secret.txt)", "Edit(./secret.txt)", "Write(./secret.txt)"]
  }
}

secret.txt 內放入 ORIGINAL-SECRET,每次都寫回原狀。

*父行程會帶進來 12 個 `CLAUDE_` 環境變數**,所以每次都先全部移除再執行。
我曾經忘記這件事,一口氣浪費了 24 次測試。

Get-ChildItem Env: | Where-Object { $_.Name -like "CLAUDE*" -or $_.Name -like "ANTHROPIC*" } |
  ForEach-Object { [Environment]::SetEnvironmentVariable($_.Name, $null, "Process") }

1. 用 tee 寫入時,Edit() 的拒絕沒有生效

已在 Claude Code 2.1.269(2026-09-11)修正。

明明拒絕了 Edit(./secret.txt),但透過 Bash 的 tee 卻能寫進同一個檔案。

Claude Code 2.1.2682.1.270Edit 工具寫入0/4 寫入(拒絕)0/3(拒絕)用 tee 寫入同一個檔案**4/4 寫入0/3(拒絕)今天在 2.1.282 上重新測了一次,無論是預設模式還是 bypass 模式都變成 0/2 被擋下。
這個漏洞已經補上了。**

詳細內容見
Claude Code 內拒絕編輯的檔案,用 tee 竟然 4 次全寫進去了。


2. 在 bypass 模式下,eval 與 env -C 仍然會繞過

這個還沒修好。

加上 --dangerously-skip-permissions 後,無法解析的行的檢查會消失。
拒絕規則本身還在,但只要包裝方式不同,就會被繞過。

Claude Code 2.1.282,帶拒絕規則,各 2 次。

命令預設模式bypass 模式echo HOLE-WROTE > secret.txt(直接)0/20/2(停止)echo HOLE-WROTE | tee secret.txt0/20/2(停止)**eval 'echo HOLE-WROTE > secret.txt'0/22/2 寫入成功**env -C . sh -c 'echo HOLE-WROTE > secret.txt'0/22/2 寫入成功**直接重導向會被擋下,但用 eval 包起來就能寫。
拒絕規則只守得住
能靜態解析的路徑**。

對策: 不要常態使用 bypass 模式。若非得用,請確保那個 session 裡被碰到什麼都沒關係。

詳細內容見
即使把拒絕規則全刪掉,Claude Code 仍然會擋下 eval。


3. 寫成 Write(路徑) 的拒絕規則,什麼也擋不住

這個也還沒修好。 修好的只有「Claude Code 自己寫規則時」的那一邊。

Claude Code 2.1.275 的 changelog 這樣寫:

Fixed /update-config writing Write(path) permission rules, which file permission checks
don't match, instead of Edit(path) rules

在 --permission-mode acceptEdits 下,做了 3 條規則 × 3 條路徑 × 2 個版本 × 每項 2 次 = 36 次。

拒絕規則Edit 工具Write 工具(新建)Bash 重導向只有 Write(...)**2/2 寫入2/2 寫入2/2 寫入Edit(...) 只有0/2(拒絕)0/2(拒絕)0/2兩者都寫0/20/20/2Edit(...) 會擋住 3 條路徑全部。 名字雖然是「Edit」,但連 Write 工具的新建也會擋。**

對策: 可以一行檢查自己的設定:

grep -n '"Write(' ~/.claude/settings.json ~/.claude/settings.local.json .claude/settings.json 2>/dev/null

有出現就改成 Edit(。不需要兩邊都寫。

詳細內容見
就算用 Write() 拒絕,Claude Code 仍然 12 次全都寫進去了。


4. 不受信任的資料夾中,settings.json 會整個被忽略

這次最讓我意外的是這裡。

我把 Bash(npm config:*) 這種允許規則分別放在 3 個地方來比對。每個條件 2 次。
npm config get registry 是在沒有規則時會要求確認的指令。

狀態規則放置位置Claude Code 2.1.2812.1.282未受信任.claude/settings.json0/2 執行**0/2 執行未受信任--allowedTools2/2 執行2/2 執行未受信任--settings <file>2/2 執行2/2 執行受信任.claude/settings.json2/2 執行2/2 執行同樣的規則,結果會因為放置位置而不同。** 原因會出現在標準錯誤輸出裡。

Ignoring 1 permissions.allow entry from .claude/settings.json: this workspace has not been
trusted. Run Claude Code interactively here once and accept the trust dialog, or set
projects["<path>"].hasTrustDialogAccepted: true in ~/.claude.json

--output-format stream-json 的 JSON 裡不會顯示。 如果是用 -p 或 CI 跑,很可能不會注意到。

對策: 在那個資料夾裡先開一次互動 session 並接受信任提示,或是
直接在 ~/.claude.json 裡寫入 hasTrustDialogAccepted: true。

我測的只有 allow 這一側。 deny 是否也會被同樣忽略,尚未確認。


5. grep 的萬用字元會把拒絕檔案的內容回傳出來

這是最新版仍然存在的洞。 今天我覺得最麻煩的就是這個。

在拒絕 Read(./secret.txt) 的狀態下,丟兩個指令。Claude Code 2.1.282,各 2 次。

命令結果column -t secret.txt0/2(停止)**grep -v NOTHING ./sec*.txt**2/2 內容被回傳回傳的是原始內容。

ORIGINAL-SECRET

*直接寫檔名會被擋,寫成 `sec.txt` 卻會過。
而且在預設模式就會這樣。** 不需要 bypass。

2.1.271 的 changelog 裡有類似的修正(我在 2026-09-16 取得)。

Fixed Bash permission checks skipping files a wildcard expands to when the wildcard sits in
a command's pattern or option value (for example grep -v dir/* file)

但修正的是「位於 pattern 或 option 位置的萬用字元」。
我測的檔案引數位置還是會通過。不是同一個洞。

對策: 如果你想靠 Bash 保護秘密檔案,不要只靠拒絕規則。
就像 .gitignore 一樣,只要檔名的寫法能換一下,就能繞開。


6. rm -rf $(pwd) 在沒有確認下就跑了

已在 Claude Code 2.1.280(2026-09-22)修正。

Fixed a recursive rm whose target is only command-substitution output, such as
rm -rf "$(pwd)", running unprompted in auto and --dangerously-skip-permissions mode;
it now asks even with a Bash allow rule

因為真的 rm 很危險,所以我只在丟棄用資料夾裡放了 3 個標記檔來測。
讓 $(pwd) 指向那個丟棄用資料夾後,在 bypass 模式下送出。

Claude Code 版本結果2.1.277 第 1 次3 個標記檔被刪掉了,沒有出現權限確認2.1.277 第 2 次沒有執行(模型自己要求確認)2.1.282 第 1、2 次沒有執行(模型直接說「不執行」)記錄裡留下的是這樣的對話。目錄本身因為正在使用所以沒被刪掉,只有內容被刪掉了。

rm -rf $(pwd)
Exit code 1
rm: cannot remove '/tmp/cc-holes-bench/victim-2.1.277-1': Device or resource busy

之後再 ls,標記檔已經不見了。

這裡我老實說明。 那 3 次停下來,是模型的判斷,不是權限機制。
我沒有親自觀測到 2.1.282 的修正(會跳出確認)。
我能觀測到的只有「2.1.277 時權限沒有擋住」這件事。


7. 無法解析的那一行本身,是被另一種機制擋下的

最後,補一個容易誤解的地方。

預設模式下 eval 和 env -C 會被擋下,不是因為拒絕規則。
就算一行拒絕規則都不寫,直接送同樣的指令,也會因為同樣的理由被擋下。

設定eval``env -C有拒絕規則會停下會停下沒有拒絕規則**會停下**會停下(這兩行是 2026-09-16 在 Claude Code 2.1.268 / 2.1.271 / 2.1.273 測到的值。今天的測試只在放了拒絕規則的狀態下進行)

擋下它的是前面的 Bash 靜態分析。

'eval' evaluates arguments as shell code
env with -C flag cannot be statically analyzed

所以,一旦 bypass 模式把這道關卡拿掉,只靠拒絕規則就擋不住了(項目 2)。


可以直接使用的確認步驟

要知道自己現在的設定狀態,看這 3 個地方就夠了。

1. 有沒有寫成不會生效的規則

grep -n '"Write(' ~/.claude/settings.json ~/.claude/settings.local.json .claude/settings.json 2>/dev/null

2. 這個資料夾是否被信任

最確實的方法,是在那個資料夾裡跑一次,然後看標準錯誤輸出。

claude -p "say ok" 2>&1 | grep -i "has not been trusted"

只要出現一行以上,代表那個資料夾的 settings.json 被忽略了。
如果要看 ~/.claude.json,就必須用那個資料夾的路徑去查
(只用 hasTrustDialogAccepted 查的話,會查到別的資料夾的資料)。

3. 拒絕規則是否真的有在生效

建立一個丟棄用目錄、寫上拒絕規則,然後讓它讀內容,這是最穩的。

mkdir -p /tmp/deny-check/.claude && cd /tmp/deny-check
printf 'ORIGINAL-SECRET\n' > secret.txt
printf '{"permissions":{"deny":["Read(./secret.txt)"]}}\n' > .claude/settings.json

接著請它執行 grep -v NOTHING ./sec*.txt,如果內容被回傳,就是第 5 項的狀態。


說不出來的事

  • 我測的是 Claude Code 2.1.277 / 2.1.281 / 2.1.282 三個點(更早的部分則是各篇文章中的版本)。
    中間的版本有跳過
  • 各條件只測 2 次(更早的是 3~4 次)。次數不多
  • 都是 claude -p 的單輪互動。 互動式 session 會出現確認對話框,條件不同
  • 第 6 項「停下來的 3 次」是模型判斷。 沒有觀測到權限側的修正
  • 第 4 項我測的只有 allow 這一側。deny 是否有相同待遇,尚未確認
  • 第 5 項是檔案引數位置的萬用字元。changelog 修的是
    pattern/option 位置,不是同一個洞
  • 2.1.274 的特殊 shell 變數與 worktree 的巢狀展開都尚未測量
    (手邊沒有 worktree 結構)
  • 信任是直接寫 ~/.claude.json 給的。是否等同於透過對話框接受,尚未確認
  • 這是在 Windows 10 / Git Bash 的環境下。env -C 的行為可能會因作業系統而不同

總結

2 週內有 7 個版本針對權限相關做了修正。 即便如此,最新版仍然留下了一些東西。

  • grep 的萬用字元會把被拒絕檔案的內容讀出來(預設模式,2/2)
  • 寫成 Write(...) 的拒絕規則什麼都擋不住。 要改成 Edit(...)
  • 在不受信任的資料夾中,settings.json 會整個被忽略。 只有 stderr 會有警告
  • 在 bypass 模式下,eval / env -C 仍可繞過
  • tee 的漏洞(2.1.269)與 rm -rf $(pwd)(2.1.280)已經補上
  • 預設模式下 eval 會被擋下,不是拒絕規則的功勞。 是靜態分析擋住的

拒絕規則不是「寫了就會生效」的設定。
真正是什麼在生效,只要把拒絕規則拿掉再做同樣的事,就看得出來。

先升到最新版,再用上面那 3 個步驟檢查自己的設定,這是比較實際的做法。


參考

相關文章


JQIT 的工程師中,95% 以上都是從無經驗開始錄用的。
如果有興趣,也歡迎到公司網站看看。

▶ 公司網站

我們也在招募工程師。如果你有興趣,也歡迎看看。

▶ 招募網站


原文出處:https://qiita.com/suwa_nobu/items/d21ff35b9329eecc6143


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝62   💬2  
846
🥈
NewsData
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登