他照常打開 Postman(測試 API 的桌面軟體),準備替客戶的訂單系統接一支新介面
2026 年 10 月,有使用者在 Postman 的 GitHub 回報區貼了一張標題很兇的單子(#14370),並加上 SEVERE嚴重等級標籤,代表回報者認為這是最高等級的問題;它是回報者自己貼的標籤,不代表官方已經確認 標記。內容是:他只是打開 Postman 桌面版,事後卻發現自己的 Codex 與 Claude Code(兩種 AI 寫程式助手)被裝上了 Postman 的 外掛plugin,替 AI 助手額外加上的功能包,可以帶來新技能、新指令或新連線,連 MCP一種讓 AI 助手連到外部服務的標準接頭,就像 USB 孔,插上去 AI 才能用該服務 連線也一併被打開,另外還多了一個 Postman 命令列工具。
最讓人在意的是一個叫 hook掛鉤,指在某個時機點自動執行的小動作,例如「每次開新對話就先跑這個」 的設定。Postman 的外掛登記了一個 SessionStart(開新對話)掛鉤,它的 matcher比對條件,決定在什麼時機觸發這個掛鉤;這裡的 startup|clear|compact 就是「剛啟動、清空對話、壓縮對話」三種時機都會觸發 寫成 startup|clear|compact,意思是幾乎每次你開新對話它都會執行,並把一段指示塞給 AI:「只要是稍微複雜的 API 工作,先載入 postman:api-engineer 技能再動手。」也就是說,一個你只是拿來測 API 的軟體,把自己設成了你其他 AI 工具的預設工作流程。
回報者還讀了安裝程式的原始碼,發現它在裝完外掛後會呼叫 approvePostmanHook,去取得掛鉤的 trusted_hash信任指紋;程式用一串固定長度的亂碼代表某個檔案的內容,內容一變,指紋就跟著變。把指紋記進「已信任」清單,AI 工具就不會再問你要不要允許這個掛鉤,並寫進 Codex 的設定裡。回報者很誠實地說:原始碼證明了「有嘗試替自己核准」,但設定檔裡那筆信任紀錄,並不能單獨證明是誰寫的。外掛在 Claude Code 這邊則是以 user scope使用者層級,意思是裝一次,你電腦上所有專案都會生效,而不是只限某個資料夾 安裝。
這一課的重點不是批評哪家公司,而是學會一件事:AI 助手的設定檔是「共用空地」,任何裝在你電腦上的軟體都有機會寫進去。所以你要會自己做體檢。體檢要看幾個地方:Claude Code 的 settings.json(裡面的 enabledPlugins 是「啟用中的外掛清單」)、installed_plugins.jsonClaude Code 的外掛登記簿,記錄這台電腦裝了哪些外掛、哪個版本、何時安裝、裝在哪個層級、Codex 的 hooks 與 config.toml 設定,以及 ~/.local/bin使用者自己的「小程式放置區」,放在這裡的可執行檔,只要這個資料夾在 PATH 裡,直接打名字就能執行。
| 判斷線索 | 通常是我自己裝的 | 需要查證的 |
|---|---|---|
| 名稱 | 我叫得出名字、記得為什麼裝 | 完全沒印象的名稱,或是我只裝了軟體、沒裝它的 AI 外掛 |
| 時間 | installedAt、檔案修改時間對得上我動手的那天 | 時間剛好是我打開某個軟體的那一分鐘,而且好幾個位置同時間更新 |
| 範圍 | 只在某個專案資料夾生效 | user scope(全專案生效),而我不記得選過 |
| 多處連動 | 只出現一處 | 同一個名稱同時出現在外掛清單、掛鉤、信任紀錄、~/.local/bin |
這是體檢腳本的核心。它只用「讀」的指令把四個位置列出來,不會修改任何東西。
$s = Join-Path $HOME '.claude\settings.json'(Get-Content $s -Raw | ConvertFrom-Json).enabledPlugins | Format-ListGet-Content $p -RawSelect-String -Path $t -Pattern 'hooks|trusted_hash|plugin|mcp'Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-30) } | Sort-Object LastWriteTime -Descending這是一個真實小專案:下載(或複製)檔案,照步驟在你電腦上跑起來。
# READ-ONLY audit (Windows PowerShell). Only Get-/Select-/Test- commands: nothing is written, changed or deleted.
# Labels are ASCII on purpose so the file prints correctly in any console encoding.
$h = $HOME
Write-Output '=== 1. enabledPlugins in ~/.claude/settings.json ==='
$s = Join-Path $h '.claude\settings.json'
if (Test-Path $s) { (Get-Content $s -Raw | ConvertFrom-Json).enabledPlugins | Format-List } else { Write-Output '(not found)' }
Write-Output ''
Write-Output '=== 2. ~/.claude/plugins/installed_plugins.json ==='
$p = Join-Path $h '.claude\plugins\installed_plugins.json'
if (Test-Path $p) { Get-Content $p -Raw } else { Write-Output '(not found)' }
Write-Output ''
Write-Output '=== 3. Codex hooks files and config.toml (matching lines only) ==='
$c = Join-Path $h '.codex'
if (Test-Path $c) {
Get-ChildItem $c -Recurse -File -Filter '*hooks*.json' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object LastWriteTime, FullName | Format-Table -AutoSize
$t = Join-Path $c 'config.toml'
if (Test-Path $t) { Select-String -Path $t -Pattern 'hooks|trusted_hash|plugin|mcp' | ForEach-Object { '{0}: {1}' -f $_.LineNumber, $_.Line } } else { Write-Output '(config.toml not found)' }
} else { Write-Output '(no .codex folder)' }
Write-Output ''
Write-Output '=== 4. ~/.local/bin files changed in the last 30 days (newest first) ==='
$b = Join-Path $h '.local\bin'
if (Test-Path $b) { Get-ChildItem $b -File -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-30) } | Sort-Object LastWriteTime -Descending | Select-Object LastWriteTime, Length, Name | Format-Table -AutoSize } else { Write-Output '(not found)' }
#!/bin/sh
# READ-ONLY audit (Mac/Linux). Only grep/cat/find/ls: nothing is written, changed or deleted.
echo '=== 1. enabledPlugins in ~/.claude/settings.json ==='
grep -n -A15 'enabledPlugins' "$HOME/.claude/settings.json" 2>/dev/null || echo '(not found)'
echo ''
echo '=== 2. ~/.claude/plugins/installed_plugins.json ==='
cat "$HOME/.claude/plugins/installed_plugins.json" 2>/dev/null || echo '(not found)'
echo ''
echo '=== 3. Codex hooks files and config.toml (matching lines only) ==='
find "$HOME/.codex" -type f -name '*hooks*.json' -exec ls -lt {} + 2>/dev/null
grep -n -E 'hooks|trusted_hash|plugin|mcp' "$HOME/.codex/config.toml" 2>/dev/null || echo '(no matching lines or no config.toml)'
echo ''
echo '=== 4. ~/.local/bin files changed in the last 30 days (newest first) ==='
find "$HOME/.local/bin" -maxdepth 1 -type f -mtime -30 -exec ls -lt {} + 2>/dev/null || echo '(not found)'
(這是示範輸出,數字與路徑為虛構;你的格式會因系統略有不同)
=== 1. enabledPlugins in ~/.claude/settings.json ===
superpowers@claude-plugins-official : True <- 【認得】我去年自己裝的
postman@claude-plugins-official : True <- 【不認得?】我只裝了 Postman 軟體,沒有要求裝它的 AI 外掛
=== 2. ~/.claude/plugins/installed_plugins.json ===
"postman@claude-plugins-official": [ { "scope": "user", "version": "2.0.1", "installedAt": "2026-10-24T09:34:02.610Z" } ]
^ 看三個欄位:scope=user(全專案生效)、version、installedAt(那天我有裝東西嗎?)
=== 3. Codex hooks files and config.toml (matching lines only) ===
LastWriteTime FullName
2026/10/24 09:34 C:\Users\me\.codex\plugins\postman\hooks\codex-hooks.json <- 時間與上面 installedAt 同一分鐘
41: [hooks.state."postman:SessionStart"]
42: trusted_hash = "sha256:ab12...ef" <- 有「已信任」紀錄。我沒印象點過同意,是誰寫的?先記錄、再查
=== 4. ~/.local/bin files changed in the last 30 days (newest first) ===
LastWriteTime Length Name
2026/10/24 09:34 48213 postman <- 又是同一分鐘,四個位置連成一條線
2026/10/02 20:11 9120 my-backup-tool <- 【認得】我自己放的
判讀重點:
1. 同一個名稱出現在 1、2、3、4 四段 + 時間幾乎同一分鐘 = 是同一個安裝動作順手做的,不是巧合。
2. 你能說出「何時、為何裝」的才算『我自己裝的』;說不出來的就是『不認得』。
3. 看到不認得的項目,不要急著刪。照 steps 的順序:先記錄、再查來源、最後才停用。
發現不認得項目時的處理順序(照順序做,不要跳步)
第 1 步【先記錄】(5 分鐘,完全不動設定)
a. 把體檢輸出整段複製到你自己的筆記,檔名加上今天日期。
b. 對每個不認得的項目寫一行:名稱 / 版本 / 安裝時間 / 出現在哪幾段(1~4)。
c. 順手截圖。之後要回報或還原,這是證據。
第 2 步【再查來源】(10 分鐘,只讀不改)
a. 對時間:installedAt 與檔案修改時間,那一分鐘你在做什麼?是不是剛打開或更新某個軟體?
b. 看內容:用 Get-Content(Mac/Linux 用 cat)讀掛鉤檔 codex-hooks.json,看它會印什麼指示給 AI。
c. 查官方說明:搜尋「軟體名稱 + plugin + Codex/Claude Code」,看官方文件或 GitHub issues 有沒有承認這個行為。
d. 判定:有說明且你願意用 → 保留;沒說明或你不要 → 進第 3 步。
第 3 步【最後才停用】(這是唯一會改設定的步驟,也不在體檢指令裡)
a. 先備份:把 settings.json 與 config.toml 各複製一份到別的資料夾。
b. 優先用官方方式停用:在 Claude Code 輸入 /plugin 找到該外掛並停用,而不是直接刪檔。
c. 停用後重跑體檢,確認該項目的 enabledPlugins 變成 False 或消失。
d. 若有信任紀錄或 ~/.local/bin 殘留,先停用外掛、觀察一天,沒有異常再決定是否清理,並把結果補進第 1 步的筆記。