.env 跟著 repo一起上了 GitHub
「先寫死,等一下再改。」資料庫密碼、API key、雲端金鑰先放進 .env 或程式碼裡,某天 .gitignore 少一行、或 repo 從私有改成公開,金鑰就跟著程式碼一起出去了。在最新版本刪掉,git 歷史還記得。
- 金鑰先寫進 .env 或程式碼「等一下再改」,某次 commit 就把它帶進 git 歷史;在最新版本刪掉,不等於消失。
- repo 一改公開就可能被持續掃描;私有 repo 也不保險,外包、離職者、遭入侵的帳號都看得到。
- 外洩時先撤銷再清理;平時用 GitHub secret scanning、push protection 和 Gitleaks、TruffleHog 這類開源掃描器找出與擋下,再把金鑰移出程式碼。
為了展示作品,把舊 repo 改成公開
假設有一家十人的 App 開發工作室,為了展示作品,把一個舊專案的 repo 從私有改成公開。幾天後雲端帳單出現異常,有人用他們的金鑰開了大量運算資源。追查發現,金鑰在兩年前的某個 commit 裡;後來的版本雖然刪掉了,但 git 歷史還留著。
先說清楚,這是示意情境,不是真實事件,工作室與經過都是為了說明而設。但「先寫死,等一下再改」幾乎每個工程師都說過:資料庫密碼、第三方 API key、雲端金鑰,先放進 .env 或直接寫在程式碼裡,功能跑起來最重要。然後某天 .gitignore 少了一行,或是有人把 repo 改成公開。
這篇要講的只有一件事:金鑰一旦進了 git,就要當它已經出去了。
git 會記住一切
第一層是歷史。在最新版本刪掉,不等於消失;任何一個舊 commit 都還能把它翻出來。
第二層是公開。公開 repo 會被持續掃描,金鑰一公開,就可能很快被找到並濫用。
第三層是私有也不保險。外包、離職者、被入侵的帳號,都能看到私有 repo 的完整歷史。
最後是擴散。一把金鑰往往用在很多地方,外洩後要找出所有用到它的位置,才能安全撤換;找不齊,就只能邊換邊等哪裡壞掉。
這篇示意情境裡,跟著 repo 出去的是什麼
這是示意情境,沒有實際外洩;下表列的是這類事件裡最常跟著 repo 一起出去的憑證,讓你知道一個 .env 裡可能放了幾把鑰匙。
| 服務 | 憑證類型 | 用途 | 存在形式 | 放在哪 | 權限範圍 | 狀態 |
|---|---|---|---|---|---|---|
| 雲端帳號(IAM) | 長期存取金鑰 | 讓程式部署與操作雲端資源 | 明文寫在 .env 或程式碼常數裡 | 兩年前的某個 commit;最新版本已刪,git 歷史仍在 | 至少能建立運算資源(示意情境中被拿來開大量運算資源) | 示意情境;撤銷前持續有效 |
| 同一個 .env 裡的其他密鑰(資料庫密碼、第三方 API key) | 密碼/API key | 連資料庫、呼叫第三方服務 | 明文環境變數 | 同一個 commit 的可能性高,但未逐一確認 | 依各服務而定 | 是否一起外洩無法確認 |
第二列標成缺口,是因為「同一個檔案裡的其他鑰匙有沒有也被拿走」,在示意情境裡沒有人查過。
能證明的、只能推論的
這篇是觀點文,情境是示意的,所以能證明的只有官方文件與官方公告寫明的事;情境裡的經過只能算推論,不知道的就寫成缺口。
不花錢就能做的流程
到這裡,其實已經足夠理解問題在哪。如果你想直接動手,可以跳到「今天就可以來試試」;下面是完整的六步流程。
第一步,外洩時,先撤銷再清理。 第一步是到服務商後台撤銷或輪換金鑰,而不是先改 git 歷史。改寫歷史無法收回已經被複製的內容。
第二步,掃一次所有 repo。 GitHub 的 secret scanning 會掃描所有分支的完整歷史;開源的 Gitleaks(git、dir 指令可以掃 repo 和本機目錄)、TruffleHog(README 說可以找出、驗證並分析外洩憑證)也能自己跑。
第三步,在推送前擋下來。 開啟 GitHub 的 push protection;本機也可以用 pre-commit hook 搭配開源掃描器。
第四步,標準化 .gitignore 和範本。 每個專案都放 .env.example,只有欄位名稱,沒有值;.env 一律列進 .gitignore。範本長這樣:
# .env.example:只放欄位名稱,值留空
DATABASE_URL=
PAYMENT_API_KEY=
CLOUD_ACCESS_KEY_ID=
CLOUD_SECRET_ACCESS_KEY=
第五步,金鑰移出程式碼。 正式環境改從密鑰管理服務或部署平台的環境變數讀取。
第六步,公開 repo 前做檢查。 把「掃描完整歷史」列為改成公開前的必要步驟,寫進一張 go-public-checklist-g5,沒勾完不改公開。
什麼時候不需要買工具
GitHub 平台內建功能加上開源掃描器,已經涵蓋「找出」和「擋下」兩件事。密鑰管理平台的價值在於「金鑰從哪裡來」,適合有多個環境和多人協作的團隊。
市面上大致分五類。程式碼平台內建:GitHub secret scanning 和 push protection(私有 repo 的可用性依方案而定)。開源掃描器:Gitleaks、TruffleHog。雲端原生密鑰管理:AWS Secrets Manager、Google Cloud Secret Manager。開源密鑰管理:Infisical(GitHub 上開源)、OpenBao(Linux 基金會 OpenSSF 下的開源 Vault 分支,MPL-2.0 授權)。商用密鑰管理:例如 HashiCorp Vault(現為 IBM 旗下,授權已改為 BSL)、1Password 的開發者密鑰管理(官網說可以在程式碼裡用引用取代金鑰本身,並有 GitHub Actions 等 CI/CD 整合)。
限制也要一起看:掃描器有誤判也有漏判,自訂格式的密鑰可能偵測不到;密鑰管理平台要改程式的讀取方式,導入有成本。選的時候問三件事:程式碼放在哪(在 GitHub 的話,先開內建功能)、基礎架構是不是主要在單一雲端(是的話,雲端原生的密鑰管理最省事)、有沒有自架能力(OpenBao 這類自架方案需要維運能力)。
後續更新兩則。2026/07/28,1Password 宣布 1Password Environments 正式提供,用於管理和共享專案的環境變數設定。2026/10/17 查證時,Gitleaks 的 README 寫明專案已「功能完整」,之後只發布安全修補,作者轉而開發 Betterleaks;既有用法仍可使用,長期導入時請把維護狀態納入考量。
這篇提到的項目要花多少錢
來源沒有列異常帳單金額與商用方案價格,表裡照實標成未公開或估算。
| 項目 | 用量 | 單價 | 期間 | US$ | NT$ | 舉證 |
|---|---|---|---|---|---|---|
| 示意情境中被開出的大量運算資源 | 未公開 | 未公開 | repo 公開後數天 | — | — | 示意情境,無實際帳單 |
| 對所有 repo 跑一次完整歷史掃描 估 | 工程師一人 | 估 2–4 小時 | 一次性 | — | — | 本站依流程估算,依 repo 數量而定 |
| Gitleaks/TruffleHog | 開源 | 軟體免費 | — | 0.00 | 0 | 各專案 GitHub 頁 |
| GitHub secret scanning(私有 repo) | 依方案 | 可用性與費用依方案 | — | — | — | 官方文件註明依方案而定,本文未取得價格 |
| 攻擊者撿到公開 repo 裡的金鑰 估 | 翻 git 歷史 | 幾乎零成本 | — | — | — | 本站推估;無攻擊端資料 |
| 合計(僅可證明列) | 0.00 | 0 | ||||
攻擊者那一列幾乎是零,防守那一列卻是一整個晚上的撤換與追查;這就是為什麼要在推送前擋下來。
你可以學到什麼
站在攻擊者那一側看,公開 repo 的 git 歷史是一個不會過期的寶箱。他不用猜密碼,也不用打穿任何系統,只要在你改成公開的那幾天之內翻到那個舊 commit。歷史不替任何人守密。
- 01金鑰在最新版本刪掉不等於消失,git 歷史裡的每個舊 commit 都還能把它翻出來。
- 02外洩時先到服務商後台撤銷或輪換金鑰,再清理 git;改寫歷史收不回已經被複製的內容。
- 03GitHub secret scanning 會掃所有分支的完整歷史,push protection 能在推送前擋下可辨識的密鑰,兩者都該先打開。
- 04每個專案放只有欄位名稱的 .env.example,並把 .env 列進 .gitignore,讓「先寫死」沒有機會被 commit。
- 05repo 改成公開前,一律先掃描完整歷史;這一步做了,示意情境裡的那筆帳單就不會出現。
- 01.env這篇的主角:最常放明文金鑰、也最常被誤 commit 的檔案。
- 02git 歷史刪掉的檔案還在舊 commit 裡,掃描與清理都要看完整歷史。
- 03Secret ScanningGitHub 內建的完整歷史掃描,先開它就能找出多數已知格式的密鑰。
- 04Push Protection在推送前擋下密鑰,比外洩後補救便宜得多。
- 05.gitignore少一行就會把 .env 帶進 repo,是最小也最常見的破口。
- 06.env.example只有欄位名稱的範本,讓新成員知道要設什麼,又不會帶出值。
- 07Gitleaks可以自己跑的開源掃描器,git 與 dir 指令分別掃 repo 和本機目錄;注意目前只發布安全修補。
- 08TruffleHog開源掃描器,能找出並驗證外洩憑證是否仍有效。
- 09pre-commit hook在本機 commit 前就攔下,補上平台功能管不到的那一段。
- 10密鑰管理(Secrets Manager)把金鑰移出程式碼的去處,回答「金鑰從哪裡來」。
全部唯讀,不改任何設定;做完你會知道自己有沒有同樣的破口。
- 在最重要的 repo 裡,列出歷史上所有動過 .env 的 commit(唯讀)
git log --all --oneline -- .env看到什麼代表什麼:有任何輸出,代表 .env 曾經被 commit 過,裡面的金鑰要當作已外洩處理。 - 確認 .env 有被 .gitignore 忽略(唯讀)
git check-ignore -v .env看到什麼代表什麼:印出是哪個 .gitignore 的哪一行在忽略它;沒有輸出就代表沒被忽略。 - 已安裝 Gitleaks 的話,掃一次 repo 的完整歷史,結果遮蔽顯示(唯讀)
gitleaks git --redact .看到什麼代表什麼:列出的每一筆都要到服務商後台確認是否仍有效;沒有發現也不代表沒有,掃描器會漏判。 - 打開程式碼平台該 repo 的設定 → 安全性頁,看 secret scanning 與 push protection 的狀態(唯讀,不要改)看到什麼代表什麼:顯示未開啟的,就是今天可以先打開的兩個開關。
- 列出公司所有公開 repo,標出哪些是從私有改成公開的(不用指令)看到什麼代表什麼:改過公開的 repo,都要確認改之前有沒有掃過完整歷史。
揭露:本文未受任何廠商贊助,提及的產品僅為舉例。
來源
每篇新文章一封:三點摘要、關鍵字定義、今天能做的一件事。
延伸閱讀
AI 工具已經從瀏覽器,跑進你的電腦裡了
AI 編程工具在本機執行指令,MCP server 在背景運作,代理可以讀寫檔案、呼叫 API。這些東西跑在員工電腦上,不在 IT 看得到的雲端服務清單裡。這篇講本機影子 AI 怎麼盤點、設定檔裡的金鑰怎麼收。
AI 寫程式助手需要金鑰,但模型不該看到它
讓 AI 編程工具直接跑指令、部署、呼叫 API,最直接的方法就是把金鑰貼進對話。工作完成了,那把金鑰也進了對話紀錄、工具的上下文,甚至某個你沒看過的記錄檔。這篇講怎麼讓工具用得到金鑰,模型卻看不到。
換一把金鑰,要改六個地方
顧問說金鑰要定期輪換,工程主管點頭,然後開始盤算:正式環境、測試環境、CI/CD、兩個微服務、某位同事本機的腳本,漏改一處就是半夜壞掉。結論是「下一季再說」。這篇講怎麼讓輪換從六處變成一處。