被偷三次之後,我們決定換一種方式放鑰匙
後門 Lambda、Owner 金鑰 15 天、356 天挖礦、單日 $3,901 的 Gemini 帳單——四次損失的共同點不是漏洞,是明文憑證散落各處。這篇是作者分析:為什麼我們推薦開發者把憑證交給 1Password Developer Security 這類 secrets manager,以及它擋得住什麼、擋不住什麼。
- 我們四次損失(後門 Lambda、Owner 金鑰 15 天、356 天挖礦、單日 $3,901 的 Gemini 帳單)沒有一次是系統被打穿:每一次都是一個明文憑證換了持有人。
- 1Password Developer Security 把憑證集中進保管庫,程式用 op:// 參照在執行時注入、SSH 私鑰用 Touch ID 授權、機器與 AI agent 走可撤銷的 service account——磁碟上不再留明文。
- 它不是護身符:注入進程式的憑證仍可能被讀走,雲端內部能用 IAM Role 的地方也應該用 IAM Role。我們推薦它的理由是盤點與生命週期,不是零風險。
我們不是被打穿的,是鑰匙被撿走的
把這個站寫過的四次損失排在一起看,形狀出奇一致。2025 年 7 月,沒用過的區域裡長出兩支會發 IAM 管理員帳號的 Lambda;2026 年 8 月,一把 2020 年建的服務帳戶金鑰在外面流了 15 天;同年 9 月,一台主機被挖礦挖了 356 天;10 月,一把休眠一年的 Gemini API key 單日刷出 $3,901。四起事件,沒有一起用到值得寫論文的漏洞。每一起的起點,都是一個明文字串換了持有人。
先講清楚:這篇不是業配。我們沒有收 1Password 任何費用或好處,推薦的理由全部來自我們自己的帳單。這篇要回答的只有一件事——為什麼我們認為小團隊開發者該把憑證交給 secrets manager 管,以及這個決定擋得住什麼、擋不住什麼。
證據先擺上來。下面這張表是四起事件裡具代表性的四把憑證,每一把的細節都在對應的防暴筆記裡審過:
| 服務 | 憑證類型 | 用途 | 存在形式 | 放在哪 | 權限範圍 | 狀態 |
|---|---|---|---|---|---|---|
| GCP:Service Account(IAM 服務帳戶) | Google Service KEY JSON(長期私鑰,無到期日) | 訊息機器人讀寫試算表、上傳照片 | JSON 檔明文;在部署平台則是整段 JSON 塞進環境變數 | 兩個 repo 的 git 歷史+部署平台環境變數+開發者本機(詳見 INC-2026-0827) | roles/owner,專案層級全權限 | 2020-09 建立,2026-09-13 刪除 |
| AI 服務供應商 API(語音轉文字與改寫) | 長期有效的 API key × 2 | 排程流程呼叫轉逐字稿與改寫 | 原始碼常數:明文字串寫死在排程腳本裡 | 受害主機的腳本目錄,root 可讀(詳見 INC-2026-0915) | 該供應商帳號的 API 用量與帳單 | 事件後視為外洩並撤銷 |
| Google Gemini API | API key(AIza 開頭格式,已遮蔽) | 一個最後沒上架的 iOS app 的開發用金鑰 | 明文字串,開發期存在於程式碼與設定中 | 實際外洩位置至今未確認(詳見 DISPATCH-2026-1003) | 產生時預設不綁 app、不綁 API、無用量上限 | 2026-10-03 已作廢 |
| 最初被用來建立後門 Lambda 的 AWS 憑證 | 長期存取金鑰或不經 MFA 換到的暫時憑證 | 攻擊者的初始存取 | 格式無法確認 | 未知——留存的事件摘要不足以指認是哪一把(詳見 INC-2025-0717) | 足以建立 Lambda、角色與 API Gateway | 處置時全數輪替,但無法指認 |
表裡最該看的是最後一列:事件結束一年多了,我們連「被撿走的是哪一把」都說不出來。說不出來的原因很簡單——當時根本沒有一張「我們總共有哪些鑰匙」的清單。
問題不是沒有保險箱,是鑰匙從來不在保險箱裡
四把憑證的死法各不相同,出生卻一模一樣:某個開發當下,一個明文字串被貼進 .env、寫進腳本、commit 進 repo,或塞進部署平台的環境變數欄位。從那一刻起它就開始複製自己——本機一份、git 歷史一份、平台一份、同事的機器可能再一份。沒有人記錄它存在幾處,沒有人負責它的退場,輪替的時候永遠會漏掉其中一份。這個狀態有個正式名字,叫 secrets sprawl(憑證蔓延);MITRE 把對應的攻擊手法編成 T1552 Unsecured Credentials——翻成白話是「不用偷,用撿的」。
.env 檔案值得單獨點名。它常被當成「把憑證移出程式碼」的最佳實務,但對撿鑰匙的人來說,.env 是把機密整理得更好偷:固定檔名、固定格式、一個檔案集齊一個專案的所有權限。我們的 Owner 金鑰事件裡,攻擊者從部署平台拿到的就是這樣一整組環境變數——資料庫密碼、AWS 金鑰、服務帳戶 JSON,一次到位。
所以我們要解的不是「怎麼讓字串不外洩」,是「怎麼讓磁碟上根本沒有字串」。
1Password Developer Security 在賣什麼
一句白話:它把密碼管理器的保管庫延伸到開發流程裡,讓憑證平常只存在於加密保管庫中,程式要用的那一刻才解析注入,磁碟上留下的只是一個指向保管庫的參照字串。參照本身洩漏了也沒有用——它不是憑證,只是地址。
到這裡,其實已經足夠理解我們推薦它的核心理由。如果你想先知道它擋不住什麼,可以直接往「它擋不住什麼」繼續閱讀。如果你想看各個元件實際長什麼樣子,下面進入技術細節。
深入閱讀六個元件各自解什麼問題以官方開發者文件為準,連結都在 sources
CLI 與 op:// 參照:.env 裡不再放真值,放參照。程式啟動改用 op run 包一層,1Password 在執行時解析參照、注入環境變數,行程結束即消失:
# .env 裡存的是地址,不是鑰匙
DATABASE_PASSWORD="op://Development/MySQL/password"
# 啟動時才解析注入
op run --env-file=.env -- node server.js
SSH Agent:私鑰存在保管庫,SSH 連線與 Git 簽署時由 agent 代答,每次使用在 macOS 上用 Touch ID 授權。~/.ssh 底下不再躺著複製過不知幾台機器的 id_rsa。
Environments:團隊版功能,讓專案成員共用同一組開發環境機密,不需要互傳 .env 檔,也不需要在磁碟留明文。
Service Accounts:給機器用的身分。排程、CI、自動化工具各拿一個只能讀特定保管庫的 token,可以單獨撤銷、有速率限制、有存取紀錄——取代「大家共用一把高權限金鑰」。
SDK(JavaScript/Python/Go):應用程式在執行階段直接向保管庫取值,程式碼與設定檔全程不出現真值。
Developer Watchtower 與 Credential Broker:前者掃描團隊裝置上未受管的憑證,告訴你蔓延集中在哪裡、該找誰收斂;後者給 AI agent 有時效、受控的憑證存取——agent 要用的權限由仲介發放,不是把 key 貼進提示詞或設定檔。
對著我們自己的四起事件打分數
推薦一個工具最誠實的方式,是回頭問:如果當時就在用,四起事件會怎樣?這一段全部是推論,不是證明——歷史沒有對照組。
Owner 金鑰那把 KEY JSON,當年是以檔案形式進了 git 歷史、以明文塞進部署平台變數。如果它只存在於保管庫、repo 與平台上只有 op:// 參照,那兩條已證實的外洩路徑都不成立。挖礦事件裡寫死在腳本的兩把 API key 同理:root 翻遍腳本目錄,拿到的會是地址而不是鑰匙。這兩起,初始路徑有很高機率被截斷。
Gemini key 那把我們不敢這樣講。洩漏點到今天還是 gap,說「在保管庫裡就不會出事」是把願望當證據。它真正對應的功能是另一個:保管庫強迫每把憑證有名字、有位置、有建立時間,一把「給沒上架的 app 用、休眠一年」的 key 在盤點裡會刺眼得多——但「會不會真的有人去看盤點」是流程問題,工具替代不了。後門 Lambda 事件也一樣:連是哪把憑證被偷都不知道的案子,任何工具都只能說「至少以後你列得出清單」。
所以打完分數,我們推薦它的理由其實不是「防偷」,是兩個更樸素的字:盤點與生命週期。每把鑰匙有地方住、有人負責、查得到還活著沒有——我們四起事件裡最貴的共同缺口,就是這個。
它擋不住什麼
這一節是整篇最重要的部分,因為 secrets manager 最危險的用法是把它當護身符。
第一,憑證注入進執行中的程式之後,保護就結束了。記憶體裡的值可以被同一台機器上的惡意程式讀走、被過度詳細的 log 印出來、被一個有執行權限的 AI agent 原樣轉述出去。356 天那台主機如果重來一次,攻擊者已經是 root 的狀態下,op run 注入的值一樣躲不掉。工具縮小的是「靜態攤在磁碟上」的暴露面,不是執行階段的暴露面。
第二,能用平台原生短期憑證的地方,輪不到它。AWS Lambda 掛 IAM Role、GCP 服務用 attached service account,這些短期憑證自動輪替、不落地,比任何保管庫方案都好。為了統一用 1Password 而替雲端工作負載重新發長期金鑰,是倒退——AWS 自己的最佳做法文件就把「優先用暫時憑證」寫在最前面。
第三,它不解決權限過大。Owner 金鑰事件的根因有兩層:金鑰以明文外流是一層,一個只寫試算表的 bot 拿著專案 Owner 是另一層。保管庫只管第一層,第二層還是得靠最小權限。
第四,集中本身是新的風險面。所有鑰匙住進同一個保管庫,代表那個帳號成為最高價值目標,master password 與 Secret Key 的保管、團隊成員的裝置安全,變成新的單點。正式環境的高頻服務也不適合每個請求都去保管庫取值——官方自己對 service account 設了速率限制,架構上該取一次、活在行程記憶體裡。
誠實地說完這四條,推薦依然成立:因為我們損失的四次,沒有一次是輸在這四條上,全部輸在更前面的那一步——明文躺在磁碟上,而且沒有人知道它在那裡。
如果今天開始導入,順序是這樣
當天能做的是 SSH 私鑰:把 ~/.ssh 底下的私鑰收進保管庫、開 SSH Agent、刪掉散落的 PEM 檔,GitHub、Git 主機、跳板機的連線從此走 Touch ID。這一步不動任何程式碼,風險最低,立刻少掉一整類「私鑰複製過幾台機器」的問題。
一週內做 CLI:挑一個本機專案,把 .env 的真值搬進保管庫、原地換成 op:// 參照、啟動指令包上 op run。跑順了再往其他專案複製,並依開發、測試、正式分保管庫,權限分開給。
一個月內收自動化:排程、CI、工作流程工具各發一個最小權限的 service account,淘汰共用的高權限 token。最後才是 AI agent——Cursor、MCP 這類會碰憑證的工具,改走 service account 或 Credential Broker,讓 agent 用得到權限、看不到鑰匙,並定期輪替。雲端工作負載維持 IAM Role,不要回頭發長期金鑰。
這些主張的證據等級
這是觀點文,觀點更要標清楚哪些有據、哪些是推論、哪些還不知道。
這篇提到的項目要花多少錢
這張表左邊是保險的牌價,最下面兩列是我們已經繳過的學費。對比不在金額大小,在順序——學費都是在沒買保險的狀態下繳的。
| 項目 | 用量 | 單價 | 期間 | US$ | NT$ | 舉證 |
|---|---|---|---|---|---|---|
| 1Password Individual(含開發者工具) | 1 人 | US$2.99/月(首年促銷,原價 $3.99) | 年繳一年 | 35.88 | 1,148 | 官方個人方案定價頁(2026-10-13) |
| 1Password Teams Starter Pack | 含 10 人 | US$24.95/月 | 年繳一年 | 299.40 | 9,581 | 官方商業方案定價頁(2026-10-13) |
| 1Password Business | 每人 | US$8.99/人/月 | 年繳一年(每人) | 107.88 | 3,452 | 官方商業方案定價頁(2026-10-13) |
| Secrets Automation/Connect 伺服器 | 附加功能 | 未公開牌價 | — | — | — | 官方頁僅標示為 add-on,需另洽 |
| 導入人力(SSH+CLI+保管庫整理) 估 | 估 4–8 小時 | — | 一次性 | — | — | 本站估算,依導入順序一節的範圍 |
| 學費:Owner 金鑰 15 天的已證實損失 | 異常用量 | — | 2026-08-28 → 09-12 | 146.00 | 4,672 | INC-2026-0827 帳單證據(詳見該篇 Cost 表) |
| 學費:休眠 Gemini key 單日消耗 | Gemini API 呼叫 | — | 2026-10-02 單日 | 3901.00 | 124,832 | GCP 費用頁截圖(詳見 DISPATCH-2026-1003) |
| 合計(僅可證明列) | 4490.16 | 143,685 | ||||
兩筆學費相加是 $4,047,夠付 Teams Starter Pack 十三年。這個除法我們希望你永遠不需要自己算一次。
你可以學到什麼
站在攻擊者那一側看,開發者的機器是一座整理好的倉庫:.env 檔名固定、格式固定,git 歷史保存他刪過的每一版,部署平台的變數頁一次列齊所有權限,腳本常數連搜尋都不用就攤在那裡。他的工具鏈全是現成的,掃描、驗證、入池、轉賣,每一步都自動化。你專案做不做得起來、公司賣什麼,他從頭到尾不在乎——他在乎的只有那個字串背後接著什麼:一張信用卡、一個寄信網域、一顆能開機器的雲端帳號。讓他失業的方式不是把字串藏得更巧,是讓磁碟上根本沒有字串。
- 01我們四起事件沒有一起是系統被打穿,每一起都是一個明文字串換了持有人;對小團隊來說,管好字串比追新漏洞更急。
- 02.env 不是機密管理,是把機密整理得更好偷:固定檔名、固定格式、一個檔案集齊一個專案的所有權限。
- 03secrets manager 真正的價值是盤點與生命週期——每把鑰匙有地方住、有人負責、查得到還活著沒有;防偷只是副作用。
- 04憑證注入進程式之後保護就結束了:記憶體、log、有執行權的 agent 都讀得到。工具縮小暴露面,不取代最小權限。
- 05雲端內部能用 IAM Role、attached service account 這類短期憑證的地方,就不要為了統一工具回頭發長期金鑰。
- 06推薦工具要連同它擋不住的部分一起講,否則就是在賣護身符。
- 01secrets sprawl(憑證蔓延)我們四起事件的共同根因:同一把憑證不知不覺存在五個地方,輪替永遠漏一份。
- 02op:// 參照(secret reference)整套方案的核心動作:磁碟上放地址、不放鑰匙,參照洩漏了也換不到權限。
- 03op run讓既有程式零改碼接上保管庫的那條指令:啟動時解析參照、注入環境變數、行程結束即消失。
- 041Password SSH Agent私鑰不再以檔案散落,連線時 Touch ID 授權;是導入順序裡風險最低、當天能完成的第一步。
- 05Service Account(1Password)機器身分:排程與 CI 各拿一個只能讀特定保管庫、可單獨撤銷的 token,取代共用高權限金鑰。
- 06Credential BrokerAI agent 用得到權限、看不到鑰匙的機制——key 不進提示詞,是 agent 時代最該盯的那一格。
- 07Developer Watchtower回答「我們的憑證蔓延到哪了」的盤點工具;我們後門 Lambda 案最缺的就是這張清單。
- 08IAM Role vs 長期金鑰雲端工作負載的正解是平台原生短期憑證;為了統一工具重發長期金鑰是倒退。
- 09休眠憑證沒人用不代表失效:保管庫讓「建立時間一年前、無人認領」這種 key 在盤點裡刺眼到藏不住。
- 10T1552 Unsecured Credentials這整篇對應的攻擊手法編號,白話是「不用偷,用撿的」;防的方法就是讓他沒得撿。
全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。
- 找出你機器上散落的 .env(唯讀)
find . -maxdepth 4 -name ".env*" -not -path "*/node_modules/*"看到什麼代表什麼:列出來的每一個檔案都是明文憑證存放點。數量比你以為的多,就是 secrets sprawl 的現場。 - 查 .env 有沒有進過 git 歷史(唯讀)
git log --all --oneline -- .env .env.local看到什麼代表什麼:有任何輸出,代表憑證在歷史 commit 裡;就算現在已從工作目錄刪除,撿得到歷史的人就撿得到鑰匙。 - 掃 repo 完整歷史裡的金鑰(唯讀)
gitleaks detect -s . --log-opts="--all"看到什麼代表什麼:任何命中都代表那把憑證要當成已外洩處理——repo 現在是私有的也一樣。 - 盤點你的 SSH 私鑰(唯讀)
ls -la ~/.ssh看到什麼代表什麼:每一把 id_ 開頭的私鑰,問自己兩個問題:它複製過幾台機器?少了它哪些門打不開?答不出來的那把,就是該收進保管庫的第一把。 - 挑你最重要的一把 key,寫下它現在存在的所有位置(不用指令)看到什麼代表什麼:寫得全,你的盤點及格;寫不全,這篇的建議就是為你寫的。那張清單我們取名「key-address-book-z2q」,搬進保管庫時第一把就從它上面挑。
結尾:鑰匙該有地址
四起事件教我們的事可以壓成一句:鑰匙不可怕,沒有地址的鑰匙才可怕。一把憑證該像一筆資產——有地方住、有人負責、有建立日和退場日,要用的時候才出現,不用的時候誰也看不見。
我們自己的導入走到哪了:SSH Agent 與本機 CLI 已經在用,保管庫分環境整理中;service account 收自動化、以及 AI agent 走 Credential Broker 的實測,還在排程上。上面兩個 gap——實測行為與成效——等我們跑完會回來更新。到時候哪一格從 gap 變成 proven,照實改。
來源
- 1Password Developer Security(官方產品頁)
- 1Password:CLI secret references(op:// 參照)
- 1Password:用環境變數載入機密(op run)
- 1Password:SSH Agent
- 1Password:Service Accounts(機器身分與速率限制)
- 1Password:Environments
- 1Password:Connect/Secrets Automation
- 1Password:SDK(JavaScript/Python/Go)
- 1Password 個人方案定價
- 1Password 商業方案定價
- AWS IAM 最佳做法:優先使用暫時憑證而非長期金鑰
- MITRE ATT&CK:T1552 Unsecured Credentials
- 本站防暴筆記:沒用過的區域裡有一台「自動販賣機」
- 本站防暴筆記:排程回報「找不到帳戶」,被停權的是整個專案
- 本站防暴筆記:沒事做卻很忙的主機,356 天
- 本站拆解:App 從來沒上架,帳單先衝上來了
每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。
延伸閱讀
六台主機共用一把沒上鎖的鑰匙,而通往它們的門其實一直開著
維運設計是『所有人先進跳板機、再進其他主機』,方向沒有錯。錯的是網路層從沒強制這條路,而那把讓跳板機免密碼開全叢集的 root 私鑰,沒有 passphrase、裸躺在磁碟上。這篇是作者分析:跳板機與 SSH 金鑰信任最常見的幾個誤區,以及它們為什麼看起來很安全。