// docs / security guides / hardening
如何保護由 AI 撰寫工具建置的應用
使用 Cursor、Claude Code、Lovable、Bolt、v0、Replit 或 Windsurf 建立的應用程式的逐步強化指南。四個階段:了解AI-產生的應用程式為何以不同方式失敗,立即執行程式碼庫審核,在部署時進行強化,然後繼續監控。觀點鮮明、敘事性強,帶有可以複製的真實片段。
為什麼AI-產生的應用程式失敗的方式不同
Vibe 編碼的應用程式可以是安全的。他們需要額外的審核通過,因為故障模式是結構性的,而不是粗心的:
- Cursor inlines hardcoded keys. 您要求Cursor“修復身份驗證錯誤”,它會貼上一個假定服務角色用戶端的Supabase 範例。鍵最終位於頁面組件的頂部。匿名客戶端和服務客戶端並存;兩艘船。
- Claude Code defaults to permissive CORS. 產生的 Express / Fastify 處理程序隨
cors({ origin: '*' })一起提供,因為這是獲得工作預覽的最快方法。中間件永遠不會獲得第二次傳遞。 - Lovable and v0 skip the rules file. Firestore 支援的專案產生資料模型,但很少接觸
firestore.rules。測試模式規則會靜默過期並鎖定資料庫,不會向使用者發出警告。 - Bolt skips RLS migrations. Bolt 產生一個 Supabase 模式和使用匿名金鑰的 CRUD 表面。
ENABLE ROW LEVEL SECURITY永遠不會進入遷移。匿名用戶可以讀取或寫入任何行。 - Windsurf trusts unsigned IDs. 產生的
GET /api/items/[id]讀取參數並查詢Postgres,而不驗證所有權。此模式非常常見,因此活動 active.idor-walking 探頭可以在單次掃描中顯示。
立即審計:grep 您的程式碼庫以查找風險模式
在硬化任何東西之前,先找出已經損壞的部分。這些 grep 每個都需要不到一分鐘的時間:
秘密和提供者密鑰
grep -RIn 'NEXT_PUBLIC_SUPABASE_SERVICE' src/
grep -RIn 'sk_live_\|pk_live_\|STRIPE_SECRET' src/
grep -RIn 'sk-ant-\|^sk-' src/ # Anthropic / OpenAI
grep -RIn 'AIza\|AKIA' src/ # Google / AWS
grep -RIn 'eyJh[A-Za-z0-9_-]\{20,\}' src/ # JWT-shaped strings任何命中都需要刪除加上密鑰輪換。 Provider 儀表板:Supabase → 設定 → API、Stripe → 開發人員 → API 鍵、Anthropic / OpenAI 控制臺。
資料庫存取控制
# Supabase migrations
grep -RIn 'CREATE TABLE public\.' supabase/migrations/
grep -RIn 'ENABLE ROW LEVEL SECURITY\|FORCE ROW LEVEL SECURITY' supabase/migrations/
# Firebase / Firestore
cat firestore.rules # confirm no `if true;` matches每個CREATE TABLE public.* 都需要一個相符的ENABLE ROW LEVEL SECURITY 和至少一個策略。 Firestore 規則的讀取範圍必須為 request.auth.uid。
身份驗證和會話處理
grep -RIn 'getSession()' src/ # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/伺服器渲染的路由必須使用supabase.auth.getUser() - 它與後端進行驗證。 getSession() 讀取未經驗證的 cookie。 localStorage 中的令牌可供頁面上執行的任何腳本存取。
標頭和中介軟體
# Confirm middleware location for src/ layouts
ls src/middleware.ts middleware.ts 2>&1
# Look for CSP and security headers
grep -RIn 'Content-Security-Policy\|Strict-Transport-Security' src/對於 src/ 佈局,僅選取 src/middleware.ts。如果您的中間件檔案位於專案根目錄,Next.js 會默默地忽略它,並且您的 CSP / auth-refresh 邏輯永遠不會運作。
部署時強化
一旦原始碼乾淨,就鎖定應用程式如何進入生產環境。
第 1 步:單獨的環境
Vercel:三個環境 - Production(您的產品領域)、預覽(PR/分期部署)、開發(本地)。每個都有自己的環境變數集。 Live Stripe / Anthropic / Supabase 鍵永遠不會到達預覽;預覽鍵永遠不會達到Production。分支自動推送到預覽;合併到main部署到Production。
步驟2:透過中間件嚴格CSP
產生每個請求的隨機數,然後將其註入Content-Security-Policy。當您設定 x-nonce 請求標頭時,Next.js 會自動將隨機數套用於自己的腳本標記。
// src/middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const nonce = crypto.randomUUID().replace(/-/g, '');
const csp = [
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`connect-src 'self' https://*.supabase.co`,
`object-src 'none'`,
`base-uri 'self'`,
`frame-ancestors 'none'`,
].join('; ');
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};步驟 3:在每個公共表上強制RLS
RLS 預設不啟用,並且不會對錶所有者強制執行,除非您 FORCE 它。將每個表與每個角色的明確策略配對。
-- supabase/migrations/XXXX_rls.sql
alter table public.profiles enable row level security;
alter table public.profiles force row level security;
create policy "profiles: read own"
on public.profiles for select
using (auth.uid() = id);
create policy "profiles: update own"
on public.profiles for update
using (auth.uid() = id)
with check (auth.uid() = id);步驟 4:對每個 API 路由進行僅伺服器驗證
每個狀態變更API 路由都會使用supabase.auth.getUser() 驗證呼叫者伺服器端。使用者物件成為user_id 的真實來源—永遠不要相信請求正文來設定它。
// src/app/api/items/route.ts
import { NextResponse, type NextRequest } from 'next/server';
import { createClient } from '@/lib/supabase/server';
export async function POST(request: NextRequest) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
const body = await request.json();
const { data, error } = await supabase
.from('items')
.insert({ ...body, user_id: user.id }) // server-supplied, not from body
.select()
.single();
if (error) return NextResponse.json({ error: error.message }, { status: 400 });
return NextResponse.json(data);
}第 5 步:反向代理您的分析
透過您自己的網域進行Proxying 分析可以避免廣告攔截器,並讓您的CSP connect-src 'self' 保持狹窄。相同的模式適用於 PostHog、Plausible、Umami、自訂事件接收器。
// src/app/api/posthog/[...path]/route.ts
import { type NextRequest } from 'next/server';
const UPSTREAM = 'https://us.i.posthog.com';
export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
const { path } = await params;
const url = `${UPSTREAM}/${path.join('/')}`;
return fetch(url, {
method: 'POST',
headers: { 'content-type': req.headers.get('content-type') ?? 'application/json' },
body: await req.text(),
});
}第 6 步:在身份驗證後反彈時開啟重定向防護
登入/註冊流程通常接受 next 查詢參數。拒絕任何不是同網站路徑的內容 - 以 / 開頭,而不是 // (相對於協議,將使用者傳送到場外)。
function safeNext(raw: string | null): string {
if (!raw) return '/dashboard';
if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
return raw;
}正在進行中:監控和重新掃描
每次部署都會發生漂移。將安全視為一個循環,而不是您完成的清單。
驗證您的生產域
Dashboard → Domains → 新增您的產品網域 → DNS TXT 或 HTTP- 檔案驗證(單步)。一旦經過驗證,主動掃描就變得可用,並且可以啟用計劃的重新掃描。
安排被動式重新掃描
Hobby 每天一次,Pro 每 3 小時一次,Unlimited 每小時一次。如果出現新的結果,每次運行都會向您發送電子郵件,如果您已訂閱,則會觸發 scan.completed webhook。
# Or from CI, via the REST API:
curl -X POST https://fixvibe.app/api/v1/scans \
-H "authorization: Bearer $FIXVIBE_TOKEN" \
-H "content-type: application/json" \
-d '{"target":"https://your-app.com"}'啟用API-主動掃描(可選)
如果您想要自動主動偵測(SQLi / XSS / IDOR 步行 / 等),請在儀表板 → 網域 → API 活動中為每個網域開啟它。授權是持久的,90 天到期,可立即撤銷。與 scan.active_api.first_used webhook 配對,以便啟用後的第一次自動主動掃描到達您的警報。
將調查結果連接到您的AI 工作流程中
Mint an API token at Account → API tokens, then configure the MCP server (/docs/mcp) in Claude Desktop / Cursor / Continue. Ask your agent: "Run a scan on staging and show me the highest-severity findings." The agent calls FixVibe, fetches the report, and renders categorized remediation guidance so code/config fixes become prompts and DNS/provider/manual fixes become operator steps.
即時威脅偵測 (Unlimited)
憑證透明度日誌差異顯示為您的網域頒發的新 TLS 憑證。 DNS-記錄差異捕獲未經授權的更改。 JS-bundle 秘密監控在新金鑰到達已發貨的捆綁包時就會觸發。威脅情報來源(Spamhaus、URLhaus)會報告您的網域(如果已列出)。
真實的故障模式及其修復
來自生產環境的五種模式掃描了數千個AI-生成的應用程序,每種模式都有實際的修復:
- 用戶端元件中的服務角色鍵
Symptom:
baas.supabase-service-key在生產中發現URL。 Cause: Cursor 自動完成將createClient(URL, SERVICE_ROLE_KEY)貼到 React 元件中。 Fix: 將服務用戶端移至src/lib/supabase/service.ts,import 'server-only'位於頂部;使用匿名金鑰建立並行src/lib/supabase/client.ts供客戶端使用;透過 Supabase Studio 輪調服務角色金鑰。 - Firestore 規則仍處於測試模式
Symptom:
baas.firebase-rules高嚴重性發現。 Cause: 產生的規則為allow read, write: if request.time < timestamp.date(2026, 6, 1);— 有時間限制的「允許所有」。 Fix: 將每個規則的範圍限定為經過驗證的使用者 —match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; }— 並重新部署firebase deploy --only firestore:rules。 - 允許CORS 生存到生產環境
Symptom:
active.cors高嚴重性。 Cause:產生的Express中間件:app.use(cors({ origin: '*' }))。 Fix: 將您的前端來源列入白名單:app.use(cors({ origin: ['https://your-app.com'], credentials: true }))。對於 Next.js API 路由,請在回應中明確設定Access-Control-Allow-Origin。 - RLS 啟用但不強制
Symptom: active
baas.supabase-rls報告匿名角色可以寫入公共表,即使在儀表板中啟用了RLS。 Cause:ENABLE沒有FORCE會使表所有者豁免 - 並且遷移作為所有者運行。 Fix: 將alter table public.items force row level security;附加到遷移。重新部署。 - 未簽名IDOR-walkable ID
Symptom:
active.idor-walking報告匿名用戶可以跨租戶讀取/api/items/1、/api/items/2、...。 Cause: API 處理程序信任路徑參數並在沒有所有權謂詞的情況下查詢 Postgres。 Fix: 在每個讀取查詢上新增.eq('user_id', user.id),或移至/api/users/[uid]/items/[id]範圍內的簽章 URL/UUID。
振動程式碼安全循環
我們的目標不是完美的安全性,而是完美的安全性。它消除了 AI 工具經常錯過的容易實現的目標,以便您可以保持快速交付。
- Generate fast — 使用Cursor、Claude Code、Lovable、Bolt。這就是重點。
- Audit immediately — 執行上面設定的 grep,檢查RLS,驗證CSP,檢查驗證邊界。
- Harden at deploy — 中間件、環境分離、CSP 隨機數、HSTS、僅伺服器驗證。
- Monitor — FixVibe 每天被動,每週在經過驗證的網域上活躍,Slack 的 Webhook,Unlimited 上的威脅偵測。
- Fix fast — use FixVibe coding-agent prompts for code/config findings and operator steps for DNS, provider, secret-rotation, or manual-review findings. Re-deploy, re-scan, close the loop.
後續步驟
有關DAST 與SAST 的概念背景以及為什麼AI- 生成的應用程式需要自行掃描,請閱讀AI-generated code security scanning。有關發貨前審核的快速參考,請參閱vibe coding security checklist。
