我在一個週三下午釋出了 GeminiOmni 的第一個公開版本。那是一個精簡的落地頁,只有一個可用的工具——早期的 Nano Banana 2 圖片編輯器版本——以及一個寫著“免費試用,無需註冊”的按鈕。
網站於柏林時間 14:42 上線。到 15:05,我的 Google Cloud 計費儀表盤開始尖叫。
這篇文章完整剖析了哪裡出了問題、損失了多少,以及我在當天工作結束前就做了哪些改變。如果你在構建任何用到付費 AI API 的東西,把這當作一堂免費課——大多數人不會寫那些令人尷尬的部分。
實際經過
以下是我的事件日誌中的時間線(是的,我確實有一個——推薦你也記一個):
- 14:42 部署成功。網站上線 geminiomni-ai.com。我在一個小型 Mastodon 例項上釋出了快速演示,然後去衝了杯咖啡。
- 14:58 回來看到 Slack 上來自 Google Cloud 的通知:“您當前週期的賬單已超過 5 美元。”這很令人擔憂,因為這個週期才開始了 16 分鐘。
- 15:05 登入 Gemini API 控制台。看到過去 20 分鐘內有 412 次圖片生成請求,來自三大洲的 38 個不同 IP。
- 15:08 將網站下線。登出了 API 金鑰。生成了一個新的但暫時不使用。
- 15:42 開始重寫架構。
- 18:30 釋出了帶有修復的 v2 版本。網站重新上線。
- 23:00 這四小時“冒險”的總賬單:84.30 美元。
為了提供背景,我為整個第一個月的 API 支出預算了 200 美元。我因為一個本該知道要避免的錯誤,在 17 分鐘內就燒掉了其中的 42%。
我之前做了什麼
圖片編輯器的第一個版本是這樣連線的:一個 Next.js 頁面,其中的 React 元件接受使用者的提示詞和上傳的圖片,然後直接通過瀏覽器使用 @google/genai 客戶端和一個嵌入在 NEXT_PUBLIC_GEMINI_API_KEY 中的 API 金鑰呼叫 Google 的 Gemini Image API。
是的。我把一個付費 API 金鑰放在了 NEXT_PUBLIC_* 裡。Next.js 盡職盡責地將其打包到客戶端 JavaScript 中。而任何開啟 DevTools 的人都能看到它。
我的辯解是——這個辯解並不太好——原始原型只是一個私有的 localhost 實驗。我把它作為客戶端環境變數連線是因為我當時迭代很快,不想費心去設定 API 路由。當我準備公開部署時,我改了大約四十樣東西,唯獨沒有改金鑰的位置。
這個錯誤並非只有我犯。在 2024-2025 年,我認識的好幾個人都做過類似的變體。這是我所見過的最常見的可預防 AI 成本事故。
為什麼這麼快就被抓取了
令我驚訝的細節是請求來得如此之快。我釋出的 Mastodon 例項大概有 600 個關注者,其中沒有人會惡意這麼做。流量肯定來自其他地方。
以下是我根據請求日誌推斷出的情況:
- 在上線五分鐘內,一個自動爬蟲訪問了網站,從 JavaScript 中提取了打包的
NEXT_PUBLIC_GEMINI_API_KEY,並將其釋出到一個我之前不知道的金鑰交易頻道。 - 十分鐘內,大約 38 個不同的 IP 開始使用該金鑰生成與我產品無關的圖片。我在日誌中抽樣看到的提示詞大多是通用的(“一輛紅色跑車”,“森林裡的動漫女孩”)。有少數是在用對抗性提示詞測試模型極限。
- 到了第十五分鐘,有人正在以 Google 允許的最大速度執行一個緊密迴圈來生成圖片。
這是我想讓獨立開發者明白的部分:存在著一些爬蟲,它們唯一的工作就是抓取新網站上的 API 金鑰。它們監控最近的 SSL 證書籤發,攻擊新域名,解析 JavaScript 程式碼包,並在幾分鐘內釋出發現的金鑰。你不需要出名,也不需要出現在熱門 subreddit 上。你只要在線並洩露了金鑰就行。
我當天下午推出的架構
修復是結構性的,而非表面性的。我重寫了資料流,使得沒有任何 API 金鑰會發送到瀏覽器,這個架構就是我們今天使用的:
浏览器(永远不保存密钥)
↓ POST /api/ai/generate { prompt, imageFile }
Next.js API 路由(服务端)
↓ envConfigs.gemini_api_key(保存在服务器上)
Google Gemini API三個具體改動:
1. 將 API 金鑰移至僅伺服器可用的環境變數。 將 NEXT_PUBLIC_GEMINI_API_KEY 重新命名為 GEMINI_API_KEY。Next.js 預設會拒絕將僅伺服器環境變數傳送到客戶端。雙重保險:我添加了一個啟動檢查,如果任何包含“API_KEY”或“SECRET”的環境變數名以 NEXT_PUBLIC_ 開頭,就會崩潰應用程式。
2. 建立一個 /api/ai/* 代理。 所有模型呼叫都通過一個服務端端點。瀏覽器傳送提示詞和任何使用者上傳的檔案;伺服器附加 API 金鑰,發起實際的 Gemini API 呼叫,並僅返回結果。這聽起來很明顯,但這就是整個修復方案。
3. 在代理上新增基於 IP 的速率限制。 即使金鑰安全了,我也不希望一個使用者在免費層刷屏生成來耗盡配額。代理在免費層限制每個 IP 每分鐘 10 次請求,在 Pro+ 層限制每分鐘 60 次。這本來無法防止最初的事件——攻擊者在輪換 IP——但它會讓下一類濫用行為變得困難得多。
總的程式碼變更大約是新增 180 行,刪除 90 行。從“網站下線”到“網站以新架構重新上線”,我花了 2 小時 48 分鐘。不算很好,但也不算災難。
下次我會怎麼做
誠實的答案是:永遠不要釋出一個對付費服務進行客戶端 API 呼叫的公開 AI 應用。 不能是“只是一個原型”,不能是“MVP 測試”,也不能是“給朋友的快速演示”。威脅模型不是惡意使用者,而是已經存在、專門針對這種場景的自動化基礎設施。
從這次事件中還得出了一些其他教訓:
部署前檢查程式碼包。 在釋出之前,執行 grep -r "NEXT_PUBLIC_" .next/static/ 並檢查裡面的內容。如果有任何看起來像憑證的東西,在上線前修復它。我現在將其作為一個部署前的 git 鉤子。
設定硬性的每日上限。 Google Cloud 允許你以每日美元金額為 Gemini API 設定上限。我將第一個月設定為 50 美元,之後設定為 200 美元。如果達到上限,API 會返回錯誤,我會收到一封郵件——這比讓它在一個糟糕的夜裡跑到 5000 美元要好得多。
使用成本最低的可發貨模型。 我在免費層介面上燒錢做 Nano Banana 2 4K 編輯,每次成本 0.151 美元。即使在攻擊發生之前,我的單位經濟性也很脆弱。現在我通過 Nano Banana 2K(每次編輯 0.045 美元)路由免費層的生成,而將 4K 留給 Pro+。3.4 倍的成本降低會讓 84 美元的賬單接近 25 美元。
限制提示詞長度。 其中一個濫用者傳送了包含嵌入圖片資料的大量提示詞,而 Gemini 是按 token 計費的。代理現在將傳入提示詞的大小硬性上限設為 50KB。合法使用者從未觸及這個限制;而濫用者在第一次請求時就會命中。
收據
對於任何想驗證我不是在編造的人:我 4 月 28 日的 Gemini API 控制台顯示,從 14:42 到 15:08 有 411 次請求,費用報告顯示圖片生成收費 83.96 美元,輸入 token 收費 0.34 美元。我當晚給 Google Cloud 支援發了郵件,他們拒絕退款——這很合理,金鑰確實是我的,而且我授權了它的使用。他們建議的架構正是我已經部署的那個。
關鍵結論,寫得足夠大,我用手機在房間另一頭也能看清:
AI API 金鑰是承載憑證。它們應該在伺服器上,永遠不該去其他地方。
如果你只能從這篇文章中帶走一件事,就帶走這個。
繼續閱讀
—— Lena
