為什麼把 * 放在指令的最前面或最後面通常沒問題,放在中間(子指令前)才特別容易出錯?
關鍵在於子指令的位置。以 Bash(npm run ) 這種寫法為例, 放在最後面,前面 npm run 這個包含子指令的部分完整寫死,所以這條規則清楚限制在「npm run 後面接任何東西」這個範圍,不會意外放行 npm install 這種完全不同的子指令。同樣道理, 放在最前面(例如官方範例裡的 Bash( --version))也是類似狀況,固定的部分在後面,語意上比較容易一眼看出實際涵蓋範圍。
真正容易出問題的是子指令本身也被包在 的匹配範圍裡的寫法——像 git * main 這種,子指令(log、merge、push 都算)夾在兩個固定文字中間,而 又可以匹配任意文字,子指令自然也在可以被替換的範圍內。這種寫法在視覺上「看起來」限制了頭尾,容易讓人誤以為中間也受到某種程度的限制,但實際上中間才是完全開放、決定這個指令具體要做什麼的關鍵部分。
這個問題實際上會造成什麼樣的安全風險,不只是「規則寫錯」這麼簡單嗎?
實際風險在於,一旦子指令落在 * 的匹配範圍裡,連帶會放行你原本完全沒想過要允許的 git 操作。以官方文件舉的例子來看,Bash(git * main) 會放行 git -c core.fsmonitor=<script> diff main 這種帶有 -c 參數的指令——-c 可以讓 git 在執行過程中跑一個你指定的外部程式,這已經不只是「git 操作範圍寫得太寬」的問題,而是這條原本以為只跟 git log 或類似操作有關的規則,實際上打開了一個可以執行任意外部指令的入口。
這也是官方文件特別把這個警告獨立列出來的原因——跟單純「規則太寬鬆,涵蓋了太多 git 子指令」比起來,這種寫法額外的風險在於,它會放行帶有危險參數(像 -c)的變化版本,而這些變化版本表面上看起來仍然是「跟 main 分支有關的 git 指令」,不容易在規則審查時被當成異常。
如果我的專案裡已經存在這種寫法的規則,實際上該怎麼檢查跟修正?
第一步是留意升級到 2.1.246 之後,啟動 Claude Code 時有沒有跳出這個警告——這個警告本身就是最直接的檢查方式,不需要自己手動去翻找設定檔案裡有沒有這種寫法。如果你手上有舊版的設定,也可以直接搜尋 settings.json 裡是不是有 Bash( 開頭、中間帶 *、後面接著非萬用字元文字的規則,特別留意那些看起來像「限定在某個分支」或「限定在某個子指令的變化版本」的規則。
第二步是針對每一條找到的規則,回頭確認原本的意圖是什麼——如果原意是「限制在某個子指令,參數不拘」,正確寫法是把 * 移到子指令之後(例如 git * main 改成 git log * main);如果原意其實就是「這幾種 git 操作,只要對象是 main 分支都放行」,那可能要重新考慮這個規則本身要不要存在,因為子指令不限這件事,本身就代表放行的範圍遠比一般人直覺以為的寬。
第三步是修正之後,實際用幾個你預期會被放行、跟你預期不該被放行的指令做測試,確認修正後的規則行為符合你的原意,而不是只看語法有沒有改對。
除了 git,這個「* 放在子指令前面」的問題,是不是也會發生在其他常用指令上?
會。這個問題本質上跟 git 本身無關,是任何「程式名稱 + 子指令 + 參數」這種結構的指令都可能遇到的匹配陷阱——只要一個工具的行為是由子指令決定(而不是單純的旗標參數),把 * 放在子指令之前,都會讓子指令本身落入可被替換的範圍。常見的例子包括 docker(docker run、docker exec 等子指令行為差異很大)、npm(npm install、npm run 意義完全不同)、或是任何你自己專案裡用到的、具有子指令結構的 CLI 工具。
實務上比較保險的檢查習慣,是每次寫 Bash 允許規則時,先問自己「這個指令的哪一部分,是真正決定它會做什麼事的關鍵字」,確保那個關鍵字(通常就是子指令)寫在 之前、被固定住,而不是讓它落在 可以匹配到的範圍裡——這個原則不是 git 專屬的,而是所有子指令結構的 CLI 工具都適用的通用檢查方式。
Claude Code 2.1.246(2026 年 8 月更新序列裡的一版)新增了一個容易被忽略、但實際影響權限規則正確性的啟動警告:如果你的 Bash 允許規則裡,萬用字元 * 出現在子指令(subcommand)之前——例如 Bash(git * main)——啟動時會跳出警告,因為這種寫法比你直覺以為的匹配範圍大得多。這篇解釋這個警告在提醒什麼,以及背後的匹配邏輯實際怎麼運作。
官方文件說得很直接:Bash 規則是比對「整段指令文字」,* 代表可以是任何文字,包括空白。Claude Code 會把 * 之前寫死的部分,原封不動地當成這條規則的限制範圍——這代表 * 放在哪個位置,直接決定了這條規則實際限制的是什麼。
用 git log --oneline main 這個指令來看,git 是程式本身,log 是子指令——真正決定這個程式要做什麼事的,是子指令,不是程式名稱。如果你寫 Bash(git * main),原意可能是「允許各種 log 相關的參數,只要最後接 main」,但 Claude Code 實際的比對邏輯,是把 * 之前的 git 當成限制範圍,* 之後的 main 當成另一個限制範圍,中間的 * 可以匹配包含子指令在內的任何文字。實際結果是:這條規則會放行 git merge main、git push origin main,甚至 git -c core.fsmonitor=<script> diff main 這種帶有 -c 參數、能讓 git 執行你指定程式的指令——因為 -c 也是 * 匹配範圍的一部分。反過來,單純的 git log main(沒有中間參數)反而不會被這條規則匹配到。
要達到「只允許 log 相關指令,參數不限」這個原本的意圖,正確寫法是把子指令寫在 * 之前:Bash(git log * main)。這樣一來,git 加上 log 都成為固定不變的限制範圍,* 只匹配 log 之後、main 之前的參數,像是 git log --oneline main、git log -5 main 都會被正確匹配,但 git log main(中間沒有參數的版本)不會被這條規則匹配到,也不會匹配到 git push origin main 這種完全不同的子指令。
這類規則造成的風險,不是「完全沒有限制」這麼明顯——如果一條規則完全沒有任何限制,你很容易一眼看出問題。真正的風險在於,Bash(git * main) 這種寫法「看起來」有限制(畢竟寫了 git 也寫了 main),但實際匹配範圍遠比字面呈現的寬,而這種落差不容易在規則審查時被肉眼發現。2.1.246 這個啟動警告要解決的,正是這種「規則看起來合理、實際上比預期寬鬆」的落差——如果你曾經寫過把 * 放在子指令前面的允許規則,升級後值得檢查啟動時有沒有跳出這個警告,重新確認規則實際匹配的範圍是否符合你的原意。