新同事報到,IT 開的是他自己的帳號,不是把你的密碼抄一份給他。能開哪些系統看職務,不是一次全開。
這件事我們都覺得理所當然。但很多人讓 AI 上工的時候,是直接把密碼貼進對話框的。
今天要處理的就是這件事。而且「權限」在這裡不是一件事:前四件是一般 coding agent 也有的本機與工具控制,第五件是任務授權,最外面還有受測系統本身的強制控制。
一般 coding agent 的副作用是改你的碼。它改壞了,git checkout 就回來了,最糟糕的是浪費了你半小時。
這位同事的副作用打到別人身上:
這些動作的共同點是收不回來。沒有 git checkout 可以按,而且事情發生的時候,承擔後果的不是你。
所以權限這一題不能只想「它會不會弄壞我的東西」,要想「它會不會替我對別人做事」。這也是為什麼下面那份授權分級裡,「不需要人點頭」那一層現在是空的。
讀者最常把這四件混在一起,但它們住在四個不同的檔案裡:
| # | 是什麼 | 住在哪 | 比喻 |
|---|---|---|---|
| 1 | 身分:用誰的身分登入受測產品 | 環境變數 | IT 開他自己的帳號 |
| 2 | 動作:在你機器上能做什麼 | .claude/settings*.json |
門禁卡刷得開哪幾道門 |
| 3 | 工具:裝了什麼就能做什麼 | .mcp.json |
發哪些工具給他 |
| 4 | 外部服務:能不能代表你對外做事 | Connector | 給不給公司信箱 |
規矩兩條,沒有例外。
用專用的測試帳號,不要用你自己的。 理由跟真人一樣:出事的時候要分得出是誰做的,而且你離職不會把它一起帶走。
帳密只記變數名,不記值。 config/ 裡寫的是 env:TOOLSHOP_TEST_USER,值走環境變數,檔案裡一個字元都不留。
有人會問:公開練習站的帳密本來就印在首頁上,何必這麼麻煩?因為習慣要一致。你一旦養成「反正這個不重要,貼一下沒關係」,總有一天會貼到真的那一組。分辨哪些能貼、哪些不能貼,是每次都要做一次的判斷;一律走環境變數,是一次做完的決定。
至於 storageState(把登入後的 cookie 存下來重用),現在還沒建。它省的是每輪重登的時間,但也等於把一份有效憑證落地成檔案,在還沒有清理機制之前先不用。
Claude Code 的權限規則有 allow、ask、deny 三段,寫在 .claude/settings.json(專案共用、進版控)或 .claude/settings.local.json(只有你自己的,不進版控)。
這個 repo 的專案共用那份放了 10 條 deny:
"deny": [
"Bash(rm -rf *)",
"Bash(sudo *)",
"Bash(git push --force*)",
"Bash(git push -f *)",
"Bash(git reset --hard*)",
"Bash(git clean -fd*)",
"Bash(gh pr merge*)",
"Bash(docker compose down -v*)",
"Bash(docker-compose down -v*)",
"Bash(dropdb*)"
]
後四條刻意對應到待會要講的那份禁止清單。
allow 那一段要講清楚一件事:允許不代表不用問。allow 的作用是省掉重複確認(每次跑 npx playwright test 都跳一次視窗會讓人放棄使用),不是授權它做任何事。
裝了什麼就能做什麼,沒裝就是做不到。這是最硬也最好用的一道界線 —— 它不依賴任何人的理解,沒裝就是沒有那個工具可以呼叫。
要停用某個 MCP server,關掉比刪掉好。這個 repo 的 .claude/settings.local.json 現在就只有一件事:
{
"disabledMcpjsonServers": ["playwright"]
}
.mcp.json 裡的設定留著,但這台機器上不啟用(Day 3 選了 CLI,成本差異會在 Day 7 算給你看)。留著設定的好處是換一台機器、或哪天要比較兩種做法時,不用重寫。
還有一個容易忽略的:用了外掛,工具可能是外掛帶進來的,你自己沒裝也會有。要盤點能力,得連外掛一起盤。
Connector 跟 MCP 的差別,不在技術,在後果。MCP 給的是能力,Connector 給的是代表你對外做事的資格。
這個系列最關鍵的是 GitHub:開 issue 會通知人、開 PR 會進別人的 review 佇列。這兩件事都不是「在你機器上」發生的。
上面那 10 條 deny 很有用,但不能把它當成完整的安全邊界。Claude Code 會解析常見的 shell 分隔符,像 &&、||、; 和 pipe,並逐一檢查每個子命令;萬用字元也可以放在規則中間。因此,cd /tmp && rm -rf x 並不會因為前面多了 cd 就自然繞過檢查。
真正的限制在於:permission rule 比對的是工具呼叫與命令形狀,不是底層所有可能產生相同副作用的行為。
| 你寫的規則 | 還要考慮 | 為什麼 |
|---|---|---|
Bash(rm *) |
/bin/rm、find -delete |
不同命令形狀可能產生相同副作用 |
Bash(git push --force*) |
git push origin main --force |
這條規則只涵蓋旗標緊跟在 push 後面的形狀 |
Bash(dropdb*) |
經由資料庫 client 送出的 DROP/TRUNCATE |
危險動作發生在另一個程式或遠端系統裡 |
所以 deny 值得寫,它可以攔下已知的危險命令;但更硬的保護要交給 sandbox、PreToolUse hook、受測系統本身的最小權限,以及下一節的授權分級與人審。每一層處理的風險不同,不能用一份字串清單代替全部。
前面四件一般 agent 也有,這一件是這個角色專屬的。它住在 config/governance.yaml:
tiers:
autonomous: [] # 目前沒有任何動作免人審
needs_review: # 需人審才能做
- file_local_report # 寫報告前,先列 title 加摘要給人確認
- open_pr # 開 PR 前先確認
- override_gate # 品質閘門被硬推放行時需留痕
forbidden: # 永遠禁止
- merge_pr
- reset_shared_env
- truncate_shared_db
override:
require_reason: true # 硬推閘門必須留痕(誰/何時/理由)
autonomous 是空的。這不是還沒填,是刻意的:新人第一週沒有任何事情可以不問。這一層會隨著它證明自己而慢慢長出東西,但那是第四週以後的事。
forbidden 那三條跟前面 deny 清單的後四條是同一件事的兩層寫法。為什麼要寫兩次?因為 deny 擋的是指令字串,governance.yaml 擋的是動作意圖。TRUNCATE 那條 deny 寫不出來,但 truncate_shared_db 這個意圖寫得出來,而每一支會產生副作用的 skill 動手前都要先讀它。
這兩套東西最常被混在一起,但它們是不同的兩層。
.claude/settings.json 管的是它在你電腦上能做什麼。受測環境的權限管的是它在別人的伺服器上能做什麼。後者不該只靠寫下來的約定,還要盡量由受測系統強制執行:使用專用且最小權限的帳號、隔離的測試 tenant、付款服務的 test mode、API rate limit,以及只允許清理自己建立資料的 server-side 權限。
實際會遇到的:
這些限制不是只靠你機器上的設定就能完成。若受測站沒有最小權限、隔離或 rate limit,你把 settings.json 開得再嚴,它仍可能對共用站送出一百筆註冊;charter 可以記錄授權範圍,但不能取代 server-side 的強制控制。
全域規章之外,還需要一張「這次任務」的條子。它寫在 charter 裡(Day 15 會正式介紹這份檔案):
authorization:
allowed:
- "註冊:可真的送出建立帳號,但 email 必須帶唯一時間戳標記
(例:sdet+<ts>@example.com),只建測試帳號"
- "付款:可完整走完結帳含按下最後的下單/確認鈕(demo sandbox,無真金流)"
- "安全(非破壞性):SQL injection 只驗證『能否不用密碼登入』後立即登出、
觀察密碼提示是否外洩、量測帳號鎖定閾值"
forbidden:
- "不得寫入或修改『既有』他人資料(只能動自己新建的測試帳號)"
這一段最像真實的職場:主管針對這一次的任務簽一張條子,事情做完條子就失效。
它也解釋了後面幾天的差異 —— 為什麼 Day 7 那一輪停在付款前,而 Day 10 對 with-bugs 那個 build 可以探得更深。不是同事變大膽了,是那次任務的條子寫得不一樣。
最後埋一條伏筆:不確定就問。 明天會把它變成一條明確的規則 —— 同一個障礙卡兩次就回來找人,不要自己想辦法繞過去。
權限拆成六層來想:身分走環境變數只記變數名、動作靠 settings.json 的 allow/ask/deny、能力靠裝或不裝哪些 MCP、對外做事的資格靠 Connector、任務意圖靠授權分級,最後由受測系統本身的最小權限與隔離承接真正的後果。這個角色跟一般 coding agent 最大的差別,就在於它的副作用可能落到別人與遠端系統身上。
身分 動作 工具 外部服務
env 變數 settings.json .mcp.json Connector
只記變數名 allow/ask/deny 裝了才有能力 能代表你對外
│ │ │ │
└────────────┴─────┬──────┴────────────┘
▼
這四層控制本機與外部工具
deny 只涵蓋已知的命令形狀
│
▼
第五層:config/governance.yaml
autonomous: [] ← 空的,第一週什麼都要問
needs_review ← 先列給人看再動手
forbidden ← 連權限都沒有
│
┌──────────────┴──────────────┐
▼ ▼
受測系統的強制控制 單次任務的條子
最小權限/隔離/rate limit charter 的 authorization
管真正後果 做完就失效
開他自己的帳號,不要把你的密碼抄給他,而且一律走環境變數,不分這組重不重要。deny 是護欄不是保險箱:它攔已知的命令形狀,sandbox 與 hook 補本機的強制邊界,受測系統的最小權限與隔離則管遠端後果。再用 charter 記錄這次任務獲准做什麼、用人審處理不確定性。這個角色的副作用會打到別人身上,才是「不需要人點頭」那一層到現在仍然空著的原因。
環境有了、規則有了、產品知識有了、帳號權限也有了。
明天讓它做第一件真正的事:完整走一次產品流程。
storageState 重用登入狀態、專用測試帳號的做法.claude/settings.json - 10 條 denyconfig/governance.example.yaml - 授權分級範本charters/toolshop-login-cart.yaml - 單次任務授權的實例