Day 6,我們使用 GEMINI_API_KEY 呼叫 Gemini。當時只在 Terminal 設定環境變數,沒有把金鑰寫進程式。
為什麼要多做這一步?
因為前端程式會交給使用者,Git Commit 會留在歷史紀錄,Log 也可能被集中保存。Secret 只要進入其中一個地方,就很難控制誰已經看過或複製它。
今天會用一組假金鑰做實驗。我們要確認兩件事:
Secret 是可以代表系統或身分執行敏感操作的憑證。
常見例子包含:
判斷重點不是名稱,而是取得這個值的人可以做什麼。
如果某個值只能識別公開專案,卻不能授權敏感操作,它不一定是 Secret。如果某個值可以呼叫付費 API、讀取私人資料或代表服務帳號,它就必須受到保護。
這兩種 Key 都來自 Google 服務,但用途不同。
| Key | 主要用途 | 能否放在瀏覽器 |
|---|---|---|
| Firebase Web API Key | 識別 Firebase Project,協助路由請求與計算配額 | 可以,但仍應限制可用 API |
| Gemini API Key | 驗證 Gemini API 請求並使用專案配額 | 正式環境不能放在 Client |
Firebase Web API Key 不負責保護 Firestore 資料。Firestore 依靠 Authentication、Security Rules 與 App Check 等控制措施。
Gemini API Key 則可以消耗 API 配額,也可能產生費用。Google 官方文件明確要求正式環境不要把 Gemini API Key 寫進 Web 或 Mobile Client。
同樣叫做 API Key,不代表兩者具有相同的安全性質。
今天的實驗放在:
demo-app/secret-demo/
├── index.html
├── client.js
└── check-bundle.js
client.js 包含一組不能使用的假金鑰:
const GEMINI_API_KEY = "DEMO_ONLY_KEY_DO_NOT_USE";
document.querySelector("#status").textContent =
`Client received a key with ${GEMINI_API_KEY.length} characters.`;
假設開發者認為變數沒有顯示在畫面上,使用者就看不到金鑰。
但瀏覽器必須先下載 JavaScript,才能執行這段程式。只要金鑰存在於 Client Source,使用者就能讀取它。
進入 Demo:
cd /media/mickey/777/ithome/demo-app
執行:
npm run secret:demo
這個指令會先使用 Vite 建置 secret-demo,再搜尋產生的瀏覽器 Bundle。
實際結果如下:
Source contains demo key: YES
Browser bundle contains key: YES
Git ignores local .env files: YES
Reviewer reads environment key: YES
Reviewer contains demo key value: NO
Vite 會壓縮程式並改寫變數名稱,但假金鑰仍然存在於 Bundle。壓縮不是加密,也不是存取控制。
有些前端框架允許使用 .env:
VITE_GEMINI_API_KEY=DEMO_ONLY_KEY_DO_NOT_USE
.env 可以避免開發者直接把值寫在 JavaScript,也能搭配 .gitignore 避免提交本機設定。
但 Vite 會把 VITE_ 開頭的變數注入 Client Bundle。只要前端程式使用這個變數,打包結果仍會包含金鑰。
因此:
.env可以降低誤傳到 Git 的機會,卻不能讓瀏覽器中的 Secret 保密。
正式產品應由後端呼叫 Gemini。瀏覽器只呼叫自己的 API,不直接取得 Gemini API Key。
Day 6 的 Reviewer 從環境變數讀取金鑰:
if (!process.env.GEMINI_API_KEY) {
throw new Error(
"GEMINI_API_KEY is required. Export it before running the review."
);
}
本機執行前,先在目前 Shell 設定:
export GEMINI_API_KEY="你的 Gemini API Key"
再執行:
npm run review:gemini -- firestore.rules
程式只包含環境變數名稱,不包含真正的值。
目前 .gitignore 也排除了:
.env
.env.*
!.env.example
.env.example 只能放變數名稱與明確的 Placeholder:
GEMINI_API_KEY=replace-with-your-api-key
GEMINI_MODEL=gemini-3.7-flash
不要把真正的 Key 貼進範例檔。
環境變數適合本機開發,但正式環境還要考慮更多風險。
Debug Endpoint、錯誤報告或第三方套件可能記錄 Process Environment。部署設定也可能讓不必要的人員讀取變數。
Google Cloud Secret Manager 使用 IAM 控制 Secret 存取,也能記錄存取事件與管理版本。Google 建議正式服務直接透過 Secret Manager API 或受支援的平台整合取得 Secret。
正式環境的基本流程如下:
Developer
│
└── 不取得正式 Secret
Production Service Identity
│
├── 透過 IAM 取得指定 Secret
└── 呼叫 Gemini API
Browser
│
└── 只呼叫自己的 Backend API
服務身分只應取得目前服務需要的 Secret。不要讓所有開發者、所有環境或所有服務共用同一把 Key。
Google 目前正把 Gemini API 從 Standard API Key 遷移到綁定 Service Account 的 Authorization Key。
官方文件指出:
如果你在 2026 年 9 月前建立過 Standard Key,應查看 Google AI Studio 與官方遷移說明,確認 Key 類型與限制。
Auth Key 改善身分與權限控制,但它仍然是 Secret。開發者不能把它放進 Git 或前端。
不安全。
刪除目前版本中的檔案,不會自動讓舊 Commit 消失。Fork、Clone、CI Artifact、Cache 與其他開發者的電腦也可能保留副本。
GitHub Secret Scanning 會掃描 Git History 與其他 Repository 內容,尋找已知格式的憑證。偵測工具可以縮短發現時間,但不能保證 Secret 從未被讀取。
因此,Secret 一旦進入 Git,就應先假設它已經外洩。
正確順序是:
只刪除檔案,卻繼續使用原本的 Key,沒有消除風險。
先搜尋常見的 Secret 名稱:
rg -n \
'(API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY)' \
--glob '!node_modules/**' \
--glob '!dist/**'
這個指令會產生誤報,因為程式本來就可能使用 GEMINI_API_KEY 這類環境變數名稱。檢查者還要確認程式保存的是變數名稱、Placeholder,還是真正的憑證值。
提交前也應檢查 Staged Diff:
git diff --cached
Repository 建立後,還可以啟用 GitHub Secret Scanning。公開 Repository 會自動獲得 Secret Scanning;組織的 Private 或 Internal Repository 則取決於方案與 GitHub Secret Protection 設定。
Agent 不應只搜尋看起來像 Key 的字串。它還要確認 Secret 如何產生、保存、傳遞與撤銷。
檢查範圍至少包含:
.env、憑證檔與本機設定是否被 Git 忽略。Agent 回報時不能印出完整 Secret。它只需要提供:
問題:Gemini API Key 被打包進瀏覽器 JavaScript
位置:secret-demo/client.js
證據:Client Source 宣告 GEMINI_API_KEY 常數
驗證:建置後的 Bundle 仍包含相同 Marker
影響:使用者可以擷取 Key 並消耗 Gemini API 配額
修正:由 Backend 呼叫 Gemini,Client 不取得 Key
如果需要識別不同 Secret,可以記錄遮罩值、Hash 或 Provider 提供的 Key ID。報告不應複製完整憑證。
Secret 不會因為變數名稱改變、程式被壓縮或檔案藏在 .env 就自動安全。
今天的前端打包實驗證明:Client 使用的值會進入使用者可以下載的 Bundle。
本機開發可以先使用環境變數。正式環境應使用 Secret Manager、IAM 與服務身分控制存取。Secret 如果進入 Git,則要立即輪替與撤銷,不能只刪除檔案。
明天,我們會檢查使用者輸入。即使系統沒有洩漏 Secret,未驗證的輸入仍可能造成 Injection 與惡意輸出。