指南

如何在你的 App 中妥善保管 API 金鑰

要妥善保管 API 金鑰,請把它們存為主機上的機密或環境變數,只在伺服器端程式碼中使用,並且絕對不要把它們貼到聊天、程式檔案、截圖或 Git 儲存庫中。讓每個金鑰只擁有它所需的最低權限,並在供應商那裡設定支出上限。如果金鑰曾經外洩,就進行輪替:建立新金鑰並刪除舊的,而不是只把它藏起來。

API 金鑰就是服務的密碼。你的 OpenAI 金鑰可以讓別人用你的錢使用 AI;你的 Stripe 私密金鑰可以讓別人發出退款;你的電子郵件服務金鑰可以讓別人以你的名義寄信。AI 建構工具很擅長把這些金鑰接上線,但不一定會留意它們最後會出現在哪裡,因此本指南會說明如何讓它們保持安全,以及如何進行檢查。

公開金鑰與私密金鑰

並非每個金鑰都是機密。了解兩者的差異,才不會對錯誤的金鑰過度緊張,而忽略了真正需要保護的金鑰。

類型範例能否放在瀏覽器中?
可公開金鑰(Publishable / public keys)Stripe 的 pk_… 金鑰、只限你網域使用的地圖金鑰、部分分析用 ID可以,這就是它們設計的用途
私密金鑰(Secret keys)Stripe 的 sk_…、OpenAI 與 Anthropic 金鑰、電子郵件服務金鑰、資料庫密碼絕對不行
服務或管理員金鑰(Service/admin keys)可以繞過託管資料庫存取規則的金鑰絕對不行,應視為最敏感的一類

如果你不確定某個金鑰屬於哪一類,請查看供應商的文件。拿不定主意時,就當作機密來處理。

第一步:把金鑰存為機密,而不是寫在程式碼中

金鑰應該放在主機的環境變數或機密設定中。你的程式碼會在執行時依名稱讀取它們(例如 process.env.OPENAI_API_KEY),因此金鑰的值永遠不會出現在你的檔案中。若想了解背景知識,請參閱什麼是環境變數與機密。

把每個 API 金鑰從程式碼中移到環境變數。列出你找到的每個金鑰、它原本所在的檔案,以及你使用的變數名稱。不要顯示它們的值。

接著確認沒有任何遺漏:

搜尋整個專案,包括設定檔與任何 .env 檔案,找出任何看起來像 API 金鑰或密碼的內容。回報檔案與行號,不要回報值。

第二步:私密金鑰只在伺服器端使用

這是最重要的規則。瀏覽器收到的任何內容,訪客都能讀取;頁面原始碼與 JavaScript 檔案都能在開發人員工具中看到。

因此,如果 App 需要呼叫 OpenAI,瀏覽器應該呼叫你的伺服器,再由你的伺服器使用金鑰呼叫 OpenAI。金鑰永遠不會離開伺服器。

聊天功能目前直接從瀏覽器呼叫 OpenAI。請把這個呼叫移到伺服器端的 API 路由,該路由從環境變數讀取 OPENAI_API_KEY。瀏覽器只應呼叫我們的路由。這個路由需要登入才能使用。

前端框架中有一個常見陷阱要注意:帶有公開前綴的變數,例如 Vite 的 VITE_、Next.js 的 NEXT_PUBLIC_,會被刻意打包進瀏覽器程式碼中。即使私密金鑰「放在環境變數裡」,只要使用了這些前綴,它就會外洩。

第三步:測試是否有任何外洩

不要相信設定已經生效,請實際檢查正式網站:

  1. 在瀏覽器中開啟已發布的 App。
  2. 開啟開發人員工具,然後切換到 Sources 或 Network 分頁。
  3. 在所有檔案中搜尋每個私密金鑰的開頭字元(例如 sk_ 或 sk-)。
  4. 檢查 App 發出的網路請求。如果請求直接從瀏覽器送往付費的 AI 服務供應商,就是危險信號。

如果發現私密金鑰,請把它移到伺服器端(第二步),並進行輪替(第六步),因為它已經是公開的了。

第四步:限制每個金鑰能做的事

許多供應商允許你限制金鑰的權限:

  • 範圍限定或受限的金鑰,只能執行你的 App 所需的操作。例如 Stripe 支援具有特定權限的受限金鑰。
  • 對必須公開的金鑰(例如地圖金鑰)設定網域或 IP 限制。
  • 為每個 App 與每個環境使用不同的金鑰,如此一來,一個金鑰外洩時不會暴露所有內容,撤銷其中一個也不會影響其他金鑰。
  • 開發時使用測試金鑰,只在已發布的 App 中使用正式金鑰。

第五步:為損害設定上限

即使金鑰受到良好的保護,仍可能透過你自己的 App 被濫用,例如有人用程式對你的 AI 功能呼叫數千次。

  • 在每個付費的供應商處設定支出上限或提醒。
  • 任何每次呼叫都需要花錢的功能,都要求登入。
  • 加入每位用戶的速率限制。參閱什麼是速率限制。

為 /api/chat 加入限制:每位用戶每小時最多 30 次 AI 請求。達到限制時,回傳清楚的訊息。拒絕未登入用戶的請求。

第六步:輪替外洩的金鑰

如果私密金鑰出現在不該出現的地方,例如聊天訊息、提交紀錄、截圖、支援工單或瀏覽器程式碼中,就應該假設它已經被盜用。

  1. 在供應商那裡建立新金鑰。
  2. 在你 App 的設定中更新機密。
  3. 重新部署或重新發布,讓正式 App 使用新金鑰。
  4. 在供應商那裡刪除舊金鑰。
  5. 檢查供應商的用量紀錄,看看是否有你不認識的活動。

刪除聊天訊息或 Git 提交並不足夠。Git 歷史會保留舊版本,而任何公開的內容都可能已經被複製。

常見錯誤

  • 把金鑰貼進提示中。 它現在已經存在於聊天紀錄中。請改用機密表單。
  • 在私密變數上使用公開前綴。 VITE_OPENAI_API_KEY 會把金鑰送給每一位訪客。
  • 提交 .env 檔案。 在第一次推送之前,確認它已列在 .gitignore 中。
  • 所有地方都用同一個金鑰。 測試與正式環境,以及每個 App,都應使用不同的金鑰。
  • 沒有支出上限。 外洩或被濫用的金鑰若沒有上限,可能在你發現之前就累積出龐大的帳單。
  • 隱藏而非輪替。 金鑰一旦外洩,只有換成新金鑰才能解決問題。
  • 截圖中含有金鑰。 為了求助而分享設定頁面前,請先裁切或模糊處理。

檢查清單

  • 每個金鑰都存為機密或環境變數,而不是寫在程式碼中。
  • 沒有 .env 檔案或金鑰被提交到 Git。
  • 私密金鑰只在伺服器端程式碼中使用。
  • 沒有私密變數使用 VITE_ 或 NEXT_PUBLIC_ 之類的公開前綴。
  • 我已搜尋正式網站的 JavaScript,確認沒有任何金鑰前綴。
  • 供應商允許的情況下,金鑰都已設定範圍或限制。
  • 測試與正式金鑰是分開的。
  • 付費功能需要登入,並有每位用戶的限制。
  • 每個付費供應商都設定了支出上限或提醒。
  • 我知道如何輪替每個金鑰,且任何曾經外洩的金鑰都已輪替。

在 Mythex 上妥善保管金鑰

Mythex 透過兩種方式讓金鑰遠離你的程式碼與聊天:

  • 機密請求卡片。 當 Agent 需要金鑰時,會在聊天中顯示一張卡片,讓你在表單欄位中輸入值,而不是在訊息輸入框中輸入;金鑰會存為專案機密。參閱聊天中的機密。
  • 專案設定 → Secrets,或在聊天中輸入 /secrets,讓你自行新增或變更金鑰。參閱環境變數與機密。

變更已發布 App 的機密後,請重新發布,讓正式 App 套用新的設定。匯出的專案不包含機密,因此在你下次執行程式碼的任何地方,都需要重新加入。如果金鑰曾經出現在聊天中,請在供應商那裡進行輪替。

上線前安全檢查的其餘部分,請依照AI 建構 App 的安全檢查清單逐項完成。如果你正在加入 AI 功能,如何使用 LLM API 會更深入地說明金鑰與成本。

常見問題

把 API 金鑰放在前端程式碼中安全嗎?

只有在服務供應商本來就設計為公開的金鑰時才安全,例如 Stripe 的可公開金鑰,或是限制只能用於你網域的地圖金鑰。任何可以花錢或讀取私人資料的金鑰都必須留在伺服器端,因為傳送到瀏覽器的任何內容,任何人都能讀取。

如果我把 API 金鑰貼到聊天或公開的程式碼庫中,該怎麼辦?

請進行輪替:在供應商那裡建立新金鑰,更新你 App 的機密,重新發布,然後刪除舊金鑰。只刪除訊息或提交紀錄並不足夠,因為副本可能早已存在。

API 金鑰應該存放在哪裡?

存放在你主機的環境變數或機密設定中,由伺服器端程式碼在執行時讀取。它們不應該寫入原始碼檔案、提交到 Git,或出現在截圖中。

我要如何判斷我的網站是否暴露了 API 金鑰?

開啟你的正式網站,在瀏覽器的開發人員工具中檢視頁面原始碼與 JavaScript 檔案,並搜尋金鑰的開頭字元。如果找到了,就代表已經外洩,應該移到伺服器端並進行輪替。

Mythex · Pricing · 用 AI 打造任何東西 · 網站與應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費的 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港)