FixVibe

// docs / baas security / supabase service role exposure

暴露在 JavaScript 中的 Supabase 服務角色金鑰:意義與如何找到它

Supabase 服務角色金鑰是你資料庫的主金鑰。任何持有它的人都能繞過資料列層級安全,可讀取每張資料表的每一欄,並可隨意寫入或刪除任何內容。它的設計是只存在於伺服器端程式碼中 — 絕不在瀏覽器中。當 AI 編碼工具將其發布到 JavaScript 套件時,你的資料庫實際上就是公開的。本文解釋辨識洩漏金鑰的 JWT 形狀、產生洩漏的三種 AI 工具模式、偵測後第一小時內該做什麼,以及如何在使用者之前自動掃描它。

服務角色金鑰是什麼

Supabase 為每個專案發行兩種不同的金鑰:anon 金鑰 (在較新專案中亦稱為可公開金鑰) 與 service_role 金鑰。兩者都是由專案的 JWT 機密簽署的 JSON Web Token。差別在於烘焙到 JWT 載荷中的 role 宣告 — 公開金鑰為 anon,主金鑰為 service_role。PostgREST、Supabase Storage 和 Supabase Auth 在看到 service_role 宣告時都會切換到繞過一切的模式。

jwt.io 解碼任何 Supabase 金鑰並查看載荷。服務角色 JWT 的形狀無可錯認:

服務角色 JWT 的解碼載荷 (下方以語法醒目提示區塊顯示)。

json
{
  "iss": "supabase",
  "ref": "[project-ref]",
  "role": "service_role",
  "iat": 1700000000,
  "exp": 2000000000
}

較新的 Supabase 專案會發行帶有 sb_secret_ 前綴的機密樣式金鑰,而非 JWT。行為相同 — 公開套件中任何攜帶 sb_secret_ 的內容同樣具災難性。

AI 編碼工具如何洩漏服務角色金鑰

我們在數千個 vibe 編碼應用程式中看過相同的三種模式。每一種都始於開發者請求 AI 工具協助,結束於服務金鑰被內嵌到套件中。

模式 1:帶 NEXT_PUBLIC_ 前綴的單一 .env 檔案

開發者請求 AI 工具「設定 Supabase」,並接受包含兩個金鑰的單一 .env。AI 工具 — 在大多數環境變數透過 NEXT_PUBLIC_* 暴露的語料庫上訓練 — 為兩者都加上 NEXT_PUBLIC_ 前綴。Next.js 在建置時會將符合該前綴的任何內容內嵌到用戶端套件中。發布到 Vercel,服務金鑰就會出現在 main.[hash].js 中。

模式 2:createClient 呼叫中使用了錯誤的金鑰

開發者將兩個金鑰貼到 AI 產生的 config.ts 檔案中,而 AI 錯誤地用 process.env.SUPABASE_SERVICE_ROLE_KEY 填入瀏覽器端的 createClient() 呼叫。建置拉入該變數,JWT 就落入套件中。

模式 3:種子腳本中硬編碼服務角色金鑰

開發者請求 AI 工具撰寫為資料庫填入種子資料的腳本。AI 將服務角色金鑰直接硬編碼到檔案中 (而非從環境讀取),將檔案提交到儲存庫,公開的 GitHub 儲存庫或已部署應用程式的 /scripts/seed.js 路由現在就在提供該金鑰。

FixVibe 套件掃描如何偵測洩漏

FixVibe 的套件機密檢查會下載已部署應用程式參照的每個 JavaScript 檔案 — 入口區塊、延遲載入區塊、Web Worker、Service Worker — 並透過一個偵測器執行它們,該偵測器會解碼任何符合 JWT 形狀 (eyJ[base64-header].eyJ[base64-payload].[signature]) 的內容。如果解碼的載荷包含 "role": "service_role",掃描會將其回報為嚴重發現,附帶檔案路徑與金鑰出現的確切行。同一檢查也會透過前綴比對較新的 sb_secret_* 模式。

掃描從不會使用發現的金鑰進行驗證。它識別形狀並回報洩漏 — 使用該金鑰證明可利用性將構成對你資料庫的未授權存取。證據就在 JWT 載荷本身。

偵測到 — 第一小時內該做什麼

洩漏的服務角色金鑰是執行時的緊急事件。假設金鑰已被擷取 — 攻擊者即時監控公開套件。在你輪換金鑰並稽核近期活動之前,將資料庫視為已遭入侵。

  1. 立即輪換金鑰。在 Supabase 儀表板中,前往 Project Settings → API → Service role key → Reset。舊金鑰會在幾秒內失效。任何使用該金鑰的伺服器端程式碼必須在輪換生效前更新並重新部署。
  2. 稽核近期資料庫活動。開啟儀表板中的 Database → Logs。以最近 7 天篩選。尋找針對含 PII 資料表的異常 SELECT * 查詢、大型的 UPDATEDELETE 陳述式,以及來自你已知基礎設施之外 IP 的請求。Supabase 在每個請求上都會記錄 x-real-ip 標頭。
  3. 檢查儲存物件。造訪 Storage → Logs 並檢視近期的檔案下載。洩漏的服務角色金鑰也會給予私有儲存桶繞過一切的存取權。
  4. 從原始碼控制中移除金鑰。即使在輪換後,把 JWT 留在 git 歷史中意味著它在公開儲存庫中仍可被發現。使用 git filter-repo 或 BFG Repo-Cleaner 從歷史中清除它,然後強制推送 (請先警告協作者)。
  5. 修復後重新掃描。對重新部署的應用程式執行新的 FixVibe 掃描。套件機密發現應該會被清除。確認任何區塊中都沒有 service_role JWT 和 sb_secret_* 字串。

從根本預防洩漏

結構性修復是命名紀律加上工具級護欄:

  • 絕不將服務金鑰加上 NEXT_PUBLIC_*VITE_* 或任何其他套件內嵌前綴。命名慣例就是邊界 — 每個框架都尊重它。
  • 完全將服務金鑰排除在開發者機器的 .env 之外。在部署時從機密管理工具 (Doppler、Infisical、Vercel 加密環境變數) 讀取它,絕不在本機提交。
  • <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
  • <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
  • 加入掃描建置輸出的 CI 關卡。next build 後,grep .next/static/chunks/ 輸出中的 service_role 字串。若有任何符合,讓建置失敗。
bash
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
  | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
  && echo "JWT detected in staged changes — refusing commit" \
  && exit 1

# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
  && echo "Service-role credential leaked into bundle" \
  && exit 1

常見問題

攻擊者實際上能多快找到洩漏的 Supabase 服務角色金鑰?

公開套件掃描器會在幾分鐘內掃描新部署。研究人員曾記錄到對新 Supabase 專案從首次部署到 1 小時內的有效漏洞利用。把任何服務角色暴露視為 60 分鐘的窗口,而非 60 天的窗口。

輪換金鑰夠了嗎,還是必須假設資料已遭外洩?

輪換會使洩漏的金鑰失效,但無法撤銷已被拉出的資料。如果你的資料表包含 PII、付款資料或任何受規範的資料,你可能在 GDPR (72 小時)、CCPA 或 HIPAA 下有通報義務。稽核日誌,若稽核顯示可疑存取,請諮詢法律顧問。

如果服務角色金鑰洩漏,RLS 能保護我嗎?

不能。資料列層級安全會完全被 service_role 宣告繞過。這是設計使然 — 該金鑰存在的目的正是讓後端程式碼能跳過 RLS 進行管理操作。緩解的方式是確保金鑰永遠不會到達攻擊者可以讀取的上下文。

這適用於新的 Supabase 可公開 / 機密金鑰模型 (<code>sb_publishable_</code> / <code>sb_secret_</code>) 嗎?

是的 — 同等的風險等級。sb_secret_* 金鑰是較新專案中取代服務角色 JWT 的新機密金鑰格式。套件中任何攜帶 sb_secret_* 的內容,與洩漏的服務角色 JWT 同樣具災難性。FixVibe 的套件機密偵測器會比對兩種形狀。

anon / 可公開金鑰呢 — 它在套件中安全嗎?

是的,依設計是安全的。anon 金鑰意在存在於瀏覽器中,是每個 Supabase Web 用戶端使用的金鑰。它的安全性完全取決於每張公開資料表上 RLS 是否正確設定。請參閱 Supabase RLS 掃描器 文章了解該檢查什麼。

後續步驟

對你的生產 URL 執行 FixVibe 掃描 — 套件機密檢查免費、無需註冊,並在一分鐘內回報 service_role 暴露。將此與 Supabase RLS 掃描器 文章搭配以驗證 RLS 層正在發揮作用,並與 Supabase 儲存桶安全清單 搭配以鎖定檔案存取。關於 AI 工具為何如此可靠地產生此類洩漏的背景,請閱讀 AI 編碼工具為何留下安全漏洞

// 掃描你的 baas 介面

在別人之前找到開放的資料表。

輸入一個生產 URL。FixVibe 會列舉你的應用程式通訊的 BaaS 提供者、識別其公開端點,並回報未經驗證的用戶端可以讀取或寫入什麼。免費、無需安裝、不需信用卡。

  • 免費方案 — 每月 3 次掃描,註冊免信用卡。
  • 被動 BaaS 指紋識別 — 不需要網域所有權驗證。
  • Supabase、Firebase、Clerk、Auth0、Appwrite 等。
  • 每項發現都附 AI 修復提示 — 可貼回 Cursor / Claude Code。
暴露在 JavaScript 中的 Supabase 服務角色金鑰:意義與如何找到它 — Docs · FixVibe