AI 代理只想確認一個變數在不在,CLI 把整個服務的密鑰都印進了對話紀錄
2026 年 6 月 1 日,有人在 Zeabur 官方 agent-skills repo 開了 issue #67:官方技能教 AI 代理用 variable list 查環境變數,而這個指令預設把每一個值用明文印出來。作者說,一次長時間的 Claude Code 工作階段就這樣把資料庫連線字串和第三方 API token 送進了對話紀錄。截至 10 月 17 日,issue 還開著,CLI 最新版也還沒改。
- Zeabur 官方給 AI 代理用的 zeabur-variables 技能,教代理用 variable list 檢查環境變數,並標成「Read-only」。這個指令預設把每一個值用明文印出來,沒有遮蔽選項。
- issue #67 的作者回報:只是要確認某個變數有沒有設,整個服務的資料庫連線字串和第三方 token 就進了 Claude Code 的對話紀錄。他提了兩個修法:預設遮蔽加 --reveal,或加 --keys-only。
- 截至 2026-10-17,issue 沒有維護者回應,CLI 最新版 v0.22.2 的輸出仍是 Key/Value 明文。這篇講的是一個比單一工具更大的問題:給 AI 用的工具,預設值就是安全邊界。
一個「唯讀」指令
想像你請 AI 代理幫忙確認:正式環境的服務有沒有設好 DATABASE_URL。代理照著平台官方提供的技能說明,執行了一個被標成「Read-only」的指令。指令確實沒有改任何東西,它只是把這個服務的所有環境變數,連同每一個值,完整印在終端機上:資料庫連線字串、金流密鑰、第三方 API token。代理讀到了,對話紀錄記下來了,你要的答案只是其中一行。
這不是示意情境。2026 年 6 月 1 日,一位使用者在 Zeabur 官方的 agent-skills repo 開了 issue #67,描述的就是這件事。他說,一次好幾個小時的 Claude Code 工作階段,就這樣把一條資料庫連線字串和幾個第三方 API token 送進了對話紀錄;他已經自己加了一個攔截用的 hook 當止血,但他認為根因在 CLI 的預設值。
這篇的主張只有一個:**給 AI 代理用的工具,預設值就是安全邊界。**人類看到滿畫面的密鑰會下意識別開眼睛,AI 代理不會,它會讀、會記、會在下一輪對話裡引用。
- 2026-04-12agent-skills 的 zeabur-variables 技能最後一次更新;檢查變數的建議指令是 variable list,標為 Read-onlyGitHub commit 紀錄與技能檔內容(2026-10-17 查)
- 2026-06-01issue #67 開立:建議預設遮蔽值(Option A)或加 --keys-only(Option B)GitHub issue
- 2026-06-01唯一的留言是程式碼審查機器人,列出幾個無關的 PRGitHub issue
- 2026-09-06Zeabur CLI 發布 v0.22.2,variable list 仍只輸出 Key/Value 兩欄,沒有遮蔽選項CLI 原始碼與 release 紀錄(2026-10-17 查)
- 2026-10-17本站查核:issue 仍開著、沒有維護者回應GitHub API
四個半月,沒有人回應。這不代表沒有人在意,但代表使用者不能等工具改。
問題不在指令,在預設值
到這裡,其實已經足夠理解整件事。如果你關心的是自己該怎麼做,可以直接往「今天就能收的三道閘」繼續閱讀。如果你想知道原始碼長什麼樣、為什麼簡單的遮蔽做不到,下面開始進入技術細節。
深入閱讀CLI 原始碼與 issue 提的兩個修法
先講結論:這個指令沒有任何「只列名稱」或「遮蔽值」的開關,而且輸出帶顏色碼,事後用 sed 之類的工具遮也遮不乾淨。
本站讀了 Zeabur CLI v0.22.2 的原始碼。variable list 只註冊了選服務與選環境的參數;表格輸出的欄位是 Key 和 Value,值直接放進去,沒有遮蔽;加 --json 的話,輸出也一樣包含完整的值。
issue 作者提的兩個方向:
- Option A(他偏好的,會改變既有行為):預設把值遮掉,要看明文再加
--reveal;現有腳本只要改一行,並保留一個小版本的棄用警告。 - Option B(只新增,不改既有行為):加
--keys-only,一行一個變數名稱。
他也列了其他工具的做法當前例:kubectl 對 Secret 輸出的是 base64、Vercel 的 env ls 不顯示值、Doppler 預設遮蔽並提供 --no-mask。要補一句:base64 是編碼不是保護,任何人都能解回來,它只是讓密鑰不會「一眼就被看到」。這個差別在 AI 代理身上更明顯,因為代理會很樂意幫你解碼。
修法本身不難。難的是「工具給人用」和「工具給 AI 代理用」是兩種威脅模型,而大多數 CLI 還是照前者設計的。
對話紀錄去了哪裡
一個值一旦印在 AI 代理的終端機裡,它至少會出現在三個地方:模型供應商收到的請求內容、你本機的對話紀錄檔、以及任何備份或同步這個資料夾的工具。如果你的團隊會把對話紀錄貼到 issue、分享給同事除錯、或用雲端硬碟同步家目錄,它還會出現在更多地方。
| 服務 | 憑證類型 | 用途 | 存在形式 | 放在哪 | 權限範圍 | 狀態 |
|---|---|---|---|---|---|---|
| issue 作者的服務(未具名) | 資料庫連線字串、第三方 API token | 應用程式連資料庫與外部服務 | 明文環境變數,經 CLI 輸出後成為對話紀錄中的明文 | Claude Code 對話紀錄與下游日誌 | 作者自述;種類與權限範圍未公開 | 未公開是否輪替 |
| 任何用官方技能檢查變數的服務 | 該服務的所有環境變數值 | 依各服務而定(金流、資料庫、雲端金鑰常見) | 終端機明文輸出 | AI 代理對話紀錄、模型供應商的請求、本機與同步備份 | 一次輸出就是整個服務,不只被查的那一個變數 | 工具端未修 |
第二列標成缺口,是因為沒有人知道有多少人照著這份技能做過。但如果你做過,你自己查得到。
證據等級
這篇的事實大部分可以直接在 GitHub 上核對;作者的外洩經驗是單一自述,前例比較也是他的說法。
今天就能收的三道閘
當天就能做的,是承認已經發生的部分:在你的對話紀錄資料夾裡搜一次金鑰格式(只計數,不印內容)。如果數字不是零,那幾把金鑰就要當成外洩,照輪替流程處理,而不是「反正只在我電腦裡」。
一個月內,收三道閘。第一道在 AI 代理前面:用 Claude Code 的 PreToolUse hook(或其他工具的同類機制)攔下會印出密鑰的指令,例如 variable list、env、printenv、cat .env,改成只回傳變數名稱;這條 hook 取名 deny-secret-dump-j6,之後在設定檔裡一眼找得到。第二道在流程上:「確認某個變數有沒有設」這種需求,改成只查名稱、或只查值的雜湊與末四碼。第三道在紀錄上:對話紀錄比照憑證庫,加密、限權、設保存期限,不放進會被同步出去的資料夾。
這一季,把這個問題往上游推:你用的每一個平台 CLI,有沒有只列名稱的選項?沒有的話,去 issue 按讚、或在採購評估裡寫進去。工具的預設值,是使用者人數乘上去的安全邊界。
這篇提到的項目要花多少錢
工具本身免費,代價都在「已經印出來的密鑰」那一側。
| 項目 | 用量 | 單價 | 期間 | US$ | NT$ | 舉證 |
|---|---|---|---|---|---|---|
| Zeabur CLI 與官方技能 | 開源工具 | 免費 | — | 0.00 | 0 | GitHub repo |
| 已印出密鑰的輪替 金流與資料庫密鑰的輪替常牽動停機或部署 | 依服務數與密鑰數而定 | — | 一次性 | — | — | 無公開資料 |
| 設定 PreToolUse hook 攔截會印出密鑰的指令 估:取中間值 NT$27,500 的四分之一天 | 約 0.25 人日 | 顧問日費 NT$20,000–35,000(估) | 一次性 | 214.84 | 6,875 | 本站假設值 |
| 盤點對話紀錄中的金鑰並建立保存政策 估:取中間值 NT$27,500 的半天 | 約 0.5 人日 | 顧問日費 NT$20,000–35,000(估) | 一次性+每季複查 | 429.69 | 13,750 | 本站假設值 |
| 合計(僅可證明列) | 0.00 | 0 | ||||
預防加起來不到一個人日;事後輪替那一格填不出數字,因為它取決於你印出過什麼。
你可以學到什麼
站在攻擊者的位置看:他不需要知道你用哪個平台、哪個 CLI。他只需要一份 AI 代理的對話紀錄。現在這種紀錄越來越多,也越來越少人當成機密在管。
- 01「唯讀」說的是不會改設定,不是不會外洩。會把密鑰印到終端機的指令,交給 AI 代理之前要先攔。
- 02給 AI 用的工具,預設值就是安全邊界。人會別開眼睛,AI 代理會讀、會記、會引用。
- 03已經印進對話紀錄的密鑰,要當成外洩處理並輪替,不是「反正只在我電腦裡」。
- 04確認一個變數有沒有設,只需要名稱;需要比對值的時候,比雜湊或末四碼就夠。
- 05上游沒修之前,使用者自己的 hook 和流程就是唯一的防線;同時把需求推回上游。
- 01PreToolUse hookClaude Code 在執行工具前呼叫的攔截點;上游沒修之前,這是擋住明文輸出最直接的方法。
- 02zeabur variable list本篇的主角:預設以明文輸出整個服務的環境變數值。
- 03對話紀錄(session transcript)AI 代理讀到的所有輸出都會留在這裡,包括密鑰。
- 04安全的預設值(secure by default)使用者什麼都不改時,工具的行為就該是安全的;本案剛好相反。
- 05--keys-onlyissue 提出的修法之一:只列變數名稱,滿足多數「確認有沒有設」的需求。
- 06遮蔽(masking)輸出時把值換成星號或末幾碼;issue 偏好的預設行為。
- 07base64 不是加密kubectl 輸出 Secret 用的是編碼,任何人或 AI 都能解回明文。
- 08ANSI 顏色碼讓事後用 sed 遮蔽失效的原因之一;遮蔽要在工具端做。
- 09憑證輪替(rotation)印出過的密鑰要換掉,而且要撤銷舊的,建立新的不會讓舊的失效。
- 10唯讀不等於不外洩本篇的核心區分:權限上的唯讀,和資訊上的不外洩是兩件事。
下面五步全部唯讀,只看不改。最後一步如果數字不是零,接下來要做的就不再是唯讀了。
全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。
- 看你用的 CLI 有沒有只列名稱或遮蔽的選項
npx zeabur@latest variable list --help看到什麼代表什麼:只看到選服務與環境的參數,沒有 mask、reveal 或 keys-only,代表輸出就是明文。 - 找你的 AI 代理技能、指令說明裡有沒有推薦會印出密鑰的指令
grep -rn 'variable list\|printenv\|cat .env' ~/.claude .claude 2>/dev/null | head看到什麼代表什麼:有命中的地方,就是代理可能照著執行的位置。 - 確認有沒有設 PreToolUse hook
grep -n 'PreToolUse' ~/.claude/settings.json .claude/settings.json 2>/dev/null看到什麼代表什麼:沒有命中,代表沒有任何東西擋在代理和這類指令之間。 - 計算對話紀錄裡含金鑰格式的檔案數(只計數,不印內容)
grep -rlE 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}' ~/.claude/projects | wc -l看到什麼代表什麼:0 最好;不是 0,代表至少有那麼多個紀錄檔含 AWS 或 GitHub 金鑰,要列出來輪替。 - 看對話紀錄有多大、放在哪裡、有沒有被同步
du -sh ~/.claude/projects看到什麼代表什麼:如果這個資料夾在雲端同步或備份範圍裡,密鑰也跟著出去了。
來源
每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。
延伸閱讀
介面可以變簡單,危險不能跟著變輕
把同一個平台三年內的三份公開紀錄排在一起:2023 年實習生一鍵刪掉正式叢集、2026 年 6 月 CLI 把密鑰印進 AI 對話紀錄、2026 年 8 月平台被打穿讀走使用者的環境變數。三件事的形狀一樣:平台把複雜度藏起來,危險也跟著藏起來,最後由按下按鈕的那個人承擔。這篇講我們怎麼看,以及使用者該自己守住哪一段。
第二次被打穿的,是上一次沒換的那幾把
同一個部署平台不到兩個月第二次被入侵,這次讀走約 321 萬筆環境變數、32 萬筆帳號資料,創辦人宣布年底停止營運。官方報告自己寫出關鍵:八月事件後的輪替只換了「確認外洩」的金鑰,設定儲存庫裡那幾把明文的管理權杖與雲端金鑰沒換,十月攻擊者用的正是它們。這篇拆解兩條入侵路徑,以及輪替範圍該怎麼畫。
一個使用者部署的聊天機器人,一路走到平台的主資料庫
Zeabur 在 2026 年 9 月 9 日發布 8 月 27 日資安事件的完整報告:攻擊者從一個使用者部署、對外公開的 NextChat 進門,跨過共用叢集網路、平台 API 金鑰、AWS 帳號、正式環境四道邊界,讀走部分正式資料庫裡的專案環境變數。報告也寫明:當時的監控與告警沒有發現,是隔天外部回報金鑰被盜用才知道。