
SaaS 上線前安全檢查清單:我們優先執行的 40 項檢查 (2026)
一份SaaS 上線前安全檢查清單的价值,遠勝過一堆你尚未取得的合規徽章。這裡有個令人不安的事實:大多數上線檢查清單只告訴你該保護什麼,卻從不展示如何操作。這份清單直接提供程式碼。我們基於 Next.js 和 Supabase 進行開發,曾目睹因缺少一個 tenant_id 過濾器,導致測試帳號讀取了其他客戶的資料,而 IBM 的 2024 年資料外洩成本報告指出全球平均損失高達 488 萬美元。你不需要 SOC 2 才能上線。你需要的是以下應用層基準,這些項目已分組、可執行,並對應到 OWASP 和 NIST。
重點摘要
- 你不需要 SOC 2 或滲透測試即可上線。你需要的是下方的應用層基準。
- 最危險的上線錯誤是因缺少
tenant_id檢查而導致的跨租戶資料外洩。 - 切勿自行開發認證系統。請使用 Auth.js、Clerk 或 Supabase Auth。
- 在部署前審計
NEXT_PUBLIC_前綴。這是洩露機密最快的方式。
此基準對應至 OWASP ASVS 5.0 和 NIST 安全軟體開發框架 (SSDF),這兩者是 Google 在此主題上最信賴的參考依據,值得注意的是,排名靠前的指南中鮮少引用這兩者。
你的上線前安全檢查清單(快速版)
這些是你上架前的最低安全要求,分為六類。共四十項。由上而下執行,將程式碼部分交給你的開發人員,並將下方優先級表中标記為 P0 的任何項目視為上線阻礙。
機密與設定
.env從第一次提交起就位於.gitignore中,且從未提交過。- 審計每個
NEXT_PUBLIC_和VITE_前綴;沒有任何機密內容會發送到瀏覽器。 - 伺服器機密存放在管理器中(平台環境變數、AWS Secrets Manager、Vault),而非程式碼庫中。
- 任何曾出現在 git 歷史記錄中的金鑰,在上線前都已輪換。
- 機密不出現在日誌、錯誤負載或客戶端 bundle 中。
- 你已在構建的 bundle 中搜尋即時金鑰 (
grep -r "sk_live" .next/)。
認證與存取
- 認證是基於函式庫構建(Auth.js、Clerk 或 Supabase Auth),而非手寫。
- 帳戶可使用多因素驗證 (MFA)。
- Session Cookie 設定為
Secure、HttpOnly和SameSite。 - JWT 或 session token 不儲存在
localStorage中。 - RBAC 和最小權限角色在伺服器端強制執行,不僅僅是在 UI 中隱藏。
- 密碼使用 Argon2 或 bcrypt 進行雜湊處理(僅在你自行管理認證時)。
- 針對濫用行為測試密碼重置和電子郵件驗證流程。
資料與租戶
- 每個查詢都帶有
tenant_id過濾器。 - 租戶範圍在 ORM 或儲存庫層級強制執行,而非依靠每個查詢時記住。
- 啟用行級安全性 (RLS) 並了解其失效模式。
- 每個物件 ID 端點都執行所有權檢查(這能消除 IDOR)。
tenant_id包含在快取金鑰和物件儲存路徑中。- 資料在靜態和傳輸過程中均已加密。
- 付款和 Webhook 簽名(Stripe 等)在伺服器端驗證。
依賴套件與供應鏈
npm audit或pnpm audit沒有高風險和嚴重問題(或已明確分類處理)。- 啟用 Dependabot 或 Renovate。
- 運行 Snyk 或 Socket 進行更深入的軟體組成分析 (SCA),以及惡意軟體和授權檢查。
- 鎖定檔案 (lockfile) 已提交。
- 關鍵路徑中沒有被遺棄或無人維護的套件。
- 如果你部署 Docker,則掃描容器映像。
網路與傳輸
- 全面強制執行 HTTPS,並預載 HSTS。
- 設定內容安全策略 (Content-Security-Policy)(先僅報告,再強制執行)。
- 設定
X-Content-Type-Options: nosniff和X-Frame-Options/frame-ancestors。 - 設定
Referrer-Policy和Permissions-Policy。 - CORS 使用允許列表,絕不在帶有憑證的情況下使用
*。 - 速率限制保護認證和高成本端點。
- 每個端點都使用結構描述(Zod 或類似工具)驗證輸入。
監控與回應
- 集中式審計日誌記錄誰在何時存取了什麼。
- 錯誤處理絕不向用戶洩露堆疊追蹤。
- 當出現認證異常(登入失敗激增、不可能的位置移動)時觸發警報。
- 自動備份正在運行,且你已測試過還原。
- 存在事件回應聯絡人和一頁式操作手冊。
- 正常運作時間和錯誤監控(Sentry 或同等工具)已上線。
- 你知道引入滲透測試的觸發條件。
優先修復事項
並非每個項目都會阻礙上線。此分流表按「若跳過的損害程度」對基準進行排序,讓創始人知道哪些是不可妥協的。P0 = 上線前修復,P1 = 第一週修復,P2 = 本季度內修復。
| 檢查項目 | 類別 | 若跳過後果 | 修復工作量 | 是否阻礙上線? |
|---|---|---|---|---|
| 每個查詢的跨租戶隔離 | 資料與租戶 | 一位客戶讀取另一位客戶的資料 | 中 | P0:阻礙上線 |
| 機密移出客戶端 bundle | 機密與設定 | 公開 API 金鑰、帳戶接管 | 低 | P0:阻礙上線 |
| 物件 ID 端點的所有權檢查 | 資料與租戶 | IDOR:遞增 ID 洩露記錄 | 低 | P0:阻礙上線 |
| 使用函式庫進行認證,而非手寫 | 認證與存取 | 釋出認證錯誤、損壞的 session | 中 | P0:阻礙上線 |
| 全面強制執行 HTTPS 和 HSTS | 網路與傳輸 | 透過線路竊取 Token | 低 | P0:阻礙上線 |
| npm audit 無高風險/嚴重問題 | 依賴套件 | 傳遞性依賴中的已知 CVE | 低 | P1:第一週 |
| 認證端點的速率限制 | 網路與傳輸 | 憑證填充、暴力破解 | 低 | P1:第一週 |
| 安全標頭 (CSP, HSTS, nosniff) | 網路與傳輸 | XSS、點擊劫持、MIME 攻擊 | 低 | P1:第一週 |
| 集中式審計日誌 | 監控 | 無法查看或證明發生外洩 | 中 | P1:第一週 |
| 測試過的備份還原 | 監控 | 無法還原的備份毫無意義 | 中 | P1:第一週 |
| 帳戶可用 MFA | 認證與存取 | 更容易發生帳戶接管 | 低 | P2:本季度 |
| CSP 從僅報告轉為全面強制執行 | 網路與傳輸 | 殘留的 XSS 攻擊面 | 中 | P2:本季度 |
機密與設定:是否有任何金鑰洩露到你的客戶端 Bundle 中?
上線時的機密衛生意味著憑證絕不會到達瀏覽器。Next.js 中的 NEXT_PUBLIC_ 前綴(以及 Vite 中的 VITE_)會將值發送給每位訪客,因此一個錯誤的前綴就會洩露金鑰。將 .env 排除在 git 之外,將伺服器機密放在管理器中,並在部署前 grep 你的構建輸出。
這是我們最常看到的陷阱:NEXT_PUBLIC_ 並不意味著「公開資訊」。它意味著「我實際上將此內容發送給每位訪客的瀏覽器」。以這種方式為 Stripe 機密或服务角色金鑰添加前綴,它就會存在於 bundle 中,任何打開開發者工具的人都能看到。
# .env.local (the mistake)
NEXT_PUBLIC_STRIPE_SECRET=sk_live_51H... # BAD: ships to every browser
STRIPE_SECRET_KEY=sk_live_51H... # OK: server-only
# Public (safe to expose) vs server-only
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # anon key is meant to be public
SUPABASE_SERVICE_ROLE_KEY=eyJ... # never NEXT_PUBLIC_ this
# Catch a leaked key before you deploy
grep -r "sk_live" .next/ # any hit means a secret is in your client bundle其餘的機密基準既枯燥又不可妥協:從第一次提交起 .env 就在 .gitignore 中,伺服器機密放在管理器中而非程式碼庫中,並輪換任何曾接觸過 git 歷史記錄的金鑰(刪除提交並不會取消洩露)。洩露的部署 Token 正是像 Vercel 事件 這類資安事件的開端,因此請將每個 Token 視為已列在某人的監視清單上。
認證與存取:你應該自行開發認證還是使用函式庫?
你應該自行開發認證還是使用函式庫?幾乎總是使用函式庫。Auth.js、Clerk 和 Supabase Auth 已經吸收了多年來的邊緣案例,否則你將在生產環境中重新發現這些問題:session 固定、Token 撤銷、重置流程濫用。只有在擁有安全工程師且沒有供應商適合的情況下,自行開發才是合理的,但這很罕見。
自行開發認證是每月節省 25 美元最昂貴的方式。以下是誠實的選項比較。
| 選項 | 最佳適用情況 | 內建 MFA | 預設 Session | 陷阱 |
|---|---|---|---|---|
| Auth.js (NextAuth) | 你想要免費、自託管、完全控制 | 透過供應商/附加元件 | JWT 或資料庫 | 你需承擔每個安全邊緣案例 |
| Clerk | 你想要開箱即用的 MFA、UI 和組織功能 | 是 | 受管理 | 付費方案隨活躍用戶擴展 |
| Supabase Auth | 你已經運行 Supabase 和 Postgres RLS | 是 | JWT | RLS 政策品質取決於你 |
| 自行開發 | 你擁有安全工程師且沒有供應商適合 | 你自行開發 | 你自行開發 | 大多數認證錯誤由此開始 |
兩個陷阱會讓選擇了函式庫但跳過設定的團隊陷入困境。首先,JWT 撤銷確實很難,因此被竊取的 Token 在過期前仍然有效;保持 Token 壽命短暫,並對於敏感內容偏好伺服器端 session。其次,localStorage 中的 Token 可被任何 XSS 負載竊取,因此將 session 儲存在帶有 Secure 和 SameSite 的 httpOnly Cookie 中。在伺服器上強制執行 RBAC,而不是透過在 UI 中隱藏按鈕。
如果你的 SaaS 包含 AI 或 LLM 功能,請將模型輸入也視為不受信任的認證邊界。參閱我們關於防止提示注入的指南,因為擁有工具存取權的被越獄助手是一個穿著聊天視窗的存取控制問題。
資料與租戶:如何阻止一個租戶讀取另一個租戶的資料?
租戶隔離意味著每個查詢、快取金鑰和儲存路徑都限定於當前租戶。缺少 tenant_id 過濾器會讓一位客戶讀取另一位客戶的資料,這是最危險的上線錯誤。行級安全性有所幫助,但它只是安全帶,不是力場,因此也要在每個物件 ID 端點上添加所有權檢查。
這是競爭對手未以程式碼涵蓋的部分,也是跨租戶洩漏進入生產環境的原因。修復方法始於永遠不要單獨信任 ID。將每個讀取範圍限定為呼叫者的租戶,並在資料層強制執行,這樣就不必在每個查詢時記住它。
// BAD: no tenant scope. Any valid id returns any tenant's row.
const order = await db.order.findFirst({ where: { id } });
// GOOD: scoped to the caller's tenant on every read.
const order = await db.order.findFirst({
where: { id, tenantId: session.tenantId },
});相關的失敗是 IDOR(不安全直接物件參考),OWASP API 安全 Top 10 將其歸類為 API1:破壞的物件層級授權。測試帳號在 URL 中遞增 ID 並讀取其不應看到的記錄。PortSwigger 的 Web 安全學院 提供了攻擊者如何找到這些漏洞的完整演練。修復方法是一次所有權檢查。
// GET /api/invoices/1234, a test account increments the id
// and reads another tenant's invoice. Classic BOLA.
const invoice = await db.invoice.findUnique({ where: { id } });
// Fix: verify ownership before you return anything.
if (invoice.tenantId !== session.tenantId) {
return res.status(403).json({ error: "Forbidden" });
}這裡有一個讓你保持誠實的框架:每個 IDOR 都是租戶隔離失敗,但並非每個租戶隔離失敗都是 IDOR。Postgres 行級安全性在資料庫層面捕捉了許多此類問題,但在依賴它之前,值得了解其靜默失敗模式。
-- Postgres RLS: a seatbelt, not a force field.
alter table orders enable row level security;
create policy tenant_isolation on orders
using (tenant_id = current_setting('app.tenant_id')::uuid);
-- Silent-failure trap: forget to SET app.tenant_id on a pooled
-- connection and the policy reads the PREVIOUS request's tenant.連線池污染、非同步上下文洩漏和共享快取中毒都會悄悄地擊敗 RLS,這就是為什麼 OWASP 多租戶安全速查表 告訴你也要用租戶前綴快取金鑰和儲存路徑。OWASP ASVS 5.0 的存取控制章節和 Supabase RLS 文件 是這裡值得完整閱讀的兩個參考資料。
依賴套件與供應鏈:你的 node_modules 中隱藏了什麼?
你的應用程式的安全性僅取決於其最弱的傳遞性依賴套件。在 CI 中運行 npm audit 或 pnpm audit,並在發現高風險或嚴重問題時失敗構建,然後才上線。添加 Dependabot 或 Renovate 進行自動更新,並使用 Snyk 或 Socket 進行更深入的惡意軟體和授權檢查。
陷阱在於手動運行一次審計,看到綠色通過,然後再也沒運行過。將其連接到 CI,這樣即使是你未曾觸碰的套件中出现新的 CVE,也會阻止合併。
# .github/workflows/ci.yml: block the merge on high/critical
- name: Audit dependencies
run: npm audit --audit-level=high # non-zero exit fails the jobnpm audit 文件 涵蓋了嚴重性層級和 --production 標誌,如果你想忽略僅開發環境的問題。自動掃描是基本門檻;對於更深入的靜態分析,以捕捉依賴套件掃描器遺漏的程式碼異味和注入路徑,請參閱我們的 SonarQube 評論。提交你的鎖定檔案,刪除多年未發布版本的套件,如果你部署 Docker,則掃描你的容器映像。
網路與傳輸:SaaS 實際需要哪些安全標頭?
SaaS 需要哪些安全標頭?HTTPS 加上 HSTS 和一組簡短的標頭可以關閉最容易利用的缺口。添加內容安全策略 (Content-Security-Policy)、CORS 允許列表而非萬用字元,以及對認證和高成本端點的速率限制。使用像 Zod 這樣的結構描述驗證每個輸入,以便不良負載永遠不會到達你的邏輯。
你不需要發明過的每個標頭。你需要這個簡短列表,MDN 安全標頭參考 深入解釋了每個標頭。
| 標頭 | 建議值 | 阻止什麼 |
|---|---|---|
| Strict-Transport-Security | max-age=63072000; includeSubDomains; preload | 協議降級、SSL 剝離攻擊 |
| Content-Security-Policy | default-src 'self'; 從僅報告開始 | XSS、注入腳本、資料外洩 |
| X-Content-Type-Options | nosniff | MIME 嗅探將上傳轉換為腳本 |
| X-Frame-Options / frame-ancestors | DENY(或 frame-ancestors 'none') | 透過隱藏 iframe 進行點擊劫持 |
| Referrer-Policy | strict-origin-when-cross-origin | 洩露完整 URL(及其中的 Token) |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | 流氓腳本觸及設備 API |
在邊緣一次性設定標頭,並添加速率限制器,以便腳本無法整晚暴力破解你的登入路由。
// next.config.js: security headers on every response
const securityHeaders = [
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
];
// Rate-limit auth routes (Upstash example)
const { success } = await ratelimit.limit(ip);
if (!success) return new Response("Too many requests", { status: 429 });以僅報告模式啟動你的 CSP,以免破壞你自己的應用程式,觀察違規報告幾天,然後將其切換為強制執行。將 CORS 保持在命名的允許列表中,絕不要將 * 與憑證配對。
監控與回應:你如何知道你是否已被入侵?
你無法回應你看不到的東西。在上線前,連接集中式審計日誌、針對認證異常(如登入失敗激增)的警報、帶有測試還原的自動備份,以及一頁式事件操作手冊。未經測試的備份是一種希望,而不是備份,編寫操作手冊的時間是現在,而不是事件中。
IBM 的數據顯示,識別和控制外洩的平均時間為 258 天,如果你的日誌沒有記錄誰觸摸了什麼,你就無法縮短這個數字。將它們集中化,對重要的異常情況發出警報(登入失敗激增、不可能的位置移動登入、突然的匯出量),並確保你的錯誤處理程序返回乾淨的消息,而不是映射你內部結構的堆疊追蹤。
現代外洩檢測依賴於異常監控而非靜態規則;更多關於其實際運作方式的內容,請參閱我們關於 AI 如何防止資料外洩 的文章。對於框架支持,NIST SSDF (SP 800-218) 以通俗易懂的语言列出了回應和監控實踐。在上線前測試還原,而不是在你的資料庫消失後。
我們在審查自己的上線時實際發現了什麼
當我們的團隊對構建(無論是我們自己的還是客戶的)進行上線前安全檢查時,兩個遺漏出現的频率高於其他任何問題。第一:機密透過 NEXT_PUBLIC_ 前綴進入瀏覽器,通常是某人為了使客戶端呼叫正常工作而添加前綴的第三方 API 金鑰。第二:至少有一個端點缺少其 tenant_id 範圍或所有權檢查。
租戶範圍遺漏是可怕的那個,因為應用程式看起來很正常。每個頁面都加載。只有當有人更改 URL 中的 ID 時,錯誤才會顯現。在一次審查中,GET /api/orders/:id 向任何登入用戶返回任何訂單;測試帳號透過遞增數字讀取了另一個租戶的訂單。修復方法只有兩行:在返回之前比較 order.tenantId 和 session.tenantId。
我們不會在這裡引用虛假的捕獲率。誠實且可重複的是:NEXT_PUBLIC_ 洩漏和缺少的租戶範圍是我們在幾乎每次初次審查中都會發現的兩件事,一旦你知道要尋找它們,兩者都很容易修復。這正是為什麼檢查清單將它們作為 P0 優先處理的原因。
如果你希望在上线日前讓團隊為你運行此檢查,這就是我們的工作。獲取上線前安全審查 →
關於作者
Mert Batur Gurbuz 是 Techsy.io 的聯合創始人,該團隊為 B2B 客戶交付 AI 代理、自動化系統和語音/SDR 管道。他就讀於伯明翰大學,並撰寫關於 Techsy 團隊在生產環境中實際使用的 LLM 工具棧的文章。在 LinkedIn 上聯繫。
常見問題
SaaS 上線前安全檢查清單應包含什麼?
六個類別:機密和設定(將金鑰排除在客戶端 bundle 之外)、認證和存取(使用函式庫、添加 MFA)、資料和租戶(tenant_id 範圍加上所有權檢查)、依賴套件(CI 中的 npm audit)、網路和傳輸(HTTPS、HSTS、CSP、速率限制)以及監控和回應(審計日誌、測試過的備份、操作手冊)。
我的 SaaS 足夠安全可以上線嗎?
當完成 P0 基準時,你就準備好了:機密移出客戶端 bundle、每個查詢的租戶隔離、基於函式庫的認證、帶有安全標頭的 HTTPS,以及乾淨的依賴套件掃描。完美不是標準。一個已發布、已監控且涵蓋基準的應用程式勝過一個從未上線的「完美」應用程式。
我在推出 SaaS 之前需要滲透測試嗎?
法律上不需要。如果你處理付款或個人身份信息 (PII)、目標企業買家,或者審計員或投資者要求,則優先考慮。在 MVP 階段,將精力花在應用層基準和 OWASP Top 10 上。當明顯的 IDOR 和標頭缺口已經關閉時,滲透測試會發現更多問題。
我需要 SOC 2 才能推出 SaaS 嗎?
不需要。沒有客戶會期望上周推出的初創公司擁有 SOC 2。這是企業銷售的解鎖條件,而不是上線門檻,且需要數月時間。帶著應用層基準上線,然後在真正的企業交易需要時開始 SOC 2 流程,而不是在此之前。
我應該自行開發認證還是使用像 Auth.js、Clerk 或 Supabase Auth 這樣的函式庫?
幾乎總是使用函式庫。Auth.js、Clerk 和 Supabase Auth 已經處理了導致大多數自行開發認證錯誤的 session、Token 和重置流程邊緣案例。只有在你擁有安全工程師且有供應商無法滿足的硬性要求時,自行開發才是合理的,這確實很罕見。
如何將機密排除在我的客戶端 bundle 之外?
審計每個 NEXT_PUBLIC_ 和 VITE_ 前綴,因為帶有該前綴的任何內容都會發送到瀏覽器。從第一次提交起將 .env 排除在 git 之外,將伺服器機密儲存在管理器中,並在部署前 grep 你的構建 bundle (grep -r "sk_live" .next/) 以捕獲洩露的金鑰。
如何在多租戶 SaaS 中隔離租戶資料?
在每個查詢上放置 tenant_id 過濾器,並在 ORM 或儲存庫層級強制執行,使其自動化。啟用行級安全性並了解其失效模式(池污染、非同步洩漏)。在每个物件 ID 端點添加所有權檢查以關閉 IDOR,並按租戶範圍限定快取金鑰和儲存路徑。
SaaS 在上線前需要哪些安全標頭?
至少需要:Strict-Transport-Security (HSTS)、Content-Security-Policy、X-Content-Type-Options: nosniff、X-Frame-Options 或 frame-ancestors、Referrer-Policy 和 Permissions-Policy。以僅報告模式啟動你的 CSP,審查違規情況,然後強制執行。MDN 安全標頭文件列出了每個標頭的建議值,上面的標頭表總結了每個標頭阻止的內容。
像 npm audit 或 Snyk 這樣的自動掃描足夠嗎?
必要但不充分。像 npm audit、Snyk 和 Socket 這樣的工具可以捕捉已知的 CVE 和惡意套件,但它們無法發現業務邏輯和存取控制缺陷,如 IDOR 或缺少的租戶範圍。這些需要人工、測試帳號和明確的所有權檢查。兩者都要運行:掃描器和手動檢查。
底線:一份你實際可以發布的 SaaS 安全檢查清單
你不需要完美才能上線。你需要基準。首先關閉 P0 項目:機密移出 bundle、每個查詢的租戶隔離、每個物件端點的所有權檢查、基於函式庫的認證,以及帶有標頭的 HTTPS。如果你在週五上線前只修復一件事,那就讓它是租戶隔離,因為那是會在零警告情況下洩露客戶資料的錯誤。
這裡的所有內容今天都可以運行,且都不需要合規預算。完成這 40 項檢查,將程式碼部分交給你的開發人員,然後發布。想在上線前獲得第二雙眼睛嗎?獲取免費諮詢,我們將與你一起瀏覽清單。