FixVibe

// docs / security guides / hardening

如何保護由 AI 撰寫工具建置的應用

使用 Cursor、Claude Code、Lovable、Bolt、v0、Replit 或 Windsurf 建立的應用程式的逐步強化指南。四個階段:了解AI-產生的應用程式為何以不同方式失敗,立即執行程式碼庫審核,在部署時進行強化,然後繼續監控。觀點鮮明、敘事性強,帶有可以複製的真實片段。

為什麼AI-產生的應用程式失敗的方式不同

Vibe 編碼的應用程式可以是安全的。他們需要額外的審核通過,因為故障模式是結構性的,而不是粗心的:

  • Assistants inline hardcoded keys. You ask for a fix to an auth error and get a pasted Supabase example that assumes a service-role client. The key ends up at the top of a page component. Both the anon client and the service client coexist; both ship.
  • Generated servers default to permissive CORS. Generated Express / Fastify handlers often ship with cors({ origin: '*' }) because that's the fastest way to get a working preview. The middleware never gets a second pass.
  • Rules files get skipped. Firestore-backed projects generate the data model but rarely touch firestore.rules. Test-mode rules let anyone read and overwrite data until someone replaces them.
  • RLS never enters the migration. A generated Supabase schema and CRUD surface use the anon key, but ENABLE ROW LEVEL SECURITY never enters the migration. Anonymous users can read or write any row.
  • Handlers trust IDs. A generated GET /api/items/[id] reads the param and queries Postgres without verifying ownership. Active scans on a verified domain test for this (IDOR / BOLA).

立即審計:grep 您的程式碼庫以查找風險模式

在硬化任何東西之前,先找出已經損壞的部分。這些 grep 每個都需要不到一分鐘的時間:

秘密和提供者密鑰

bash
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 控制臺。

資料庫存取控制

bash
# 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。

身份驗證和會話處理

bash
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 中的令牌可供頁面上執行的任何腳本存取。

標頭和中介軟體

bash
# 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 會自動將隨機數套用於自己的腳本標記。

ts
// 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 isn't enabled by default on tables you create in SQL or migrations. Enable it on every exposed table and pair each one with explicit policies per role — that is what stops the anon and authenticated roles. FORCE only makes the table owner obey RLS too.

sql
-- 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 的真實來源—永遠不要相信請求正文來設定它。

ts
// 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、自訂事件接收器。

ts
// 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 查詢參數。拒絕任何不是同網站路徑的內容 - 以 / 開頭,而不是 // (相對於協議,將使用者傳送到場外)。

ts
function safeNext(raw: string | null): string {
  if (!raw) return '/dashboard';
  if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
  return raw;
}

正在進行中:監控和重新掃描

每次部署都會發生漂移。將安全視為一個循環,而不是您完成的清單。

驗證您的生產域

Dashboard → Domains → add your production domain → DNS TXT or HTTP-file verification. Active scans require Hobby or above; scheduled re-scans require Pro or Unlimited.

安排被動式重新掃描

Scheduled re-scans are available on Pro and Unlimited for verified domains. Free and Hobby scans are manual. Scheduled scans use your plan allowance. Configure completion email preferences and a scan.completed webhook if needed.

bash
# 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 工作流程中

On Hobby or above, create an API token at Account → API tokens and configure the MCP server (/docs/mcp) in your coding tool. Ask your agent to run an authorized scan and inspect the highest-severity findings. Code fixes can use remediation prompts; provider and DNS fixes may need manual operator steps.

即時威脅偵測 (Unlimited)

Periodic certificate-transparency, DNS, JS-bundle, and threat-intelligence checks report observed changes on supported signals. Alerts depend on successful polling and source availability; they do not establish continuous or complete security coverage.

真實的故障模式及其修復

Five common patterns in AI-generated apps, each with the actual fix:

  1. 用戶端元件中的服務角色鍵

    Symptom: FixVibe reports an exposed Supabase service-role key on the production URL. Cause: an autocomplete pasted createClient(URL, SERVICE_ROLE_KEY) into a React component. Fix: move the service client to src/lib/supabase/service.ts with import 'server-only' at the top; create a parallel src/lib/supabase/client.ts using the anon key for client-side use; rotate the service-role key via Supabase Studio.

  2. Firestore 規則仍處於測試模式

    Symptom: a high-severity open Firebase rules finding. Cause: generated rules read allow read, write: if request.time < timestamp.date(2026, 6, 1); — a time-bounded "allow all". Fix: scope each rule to the authenticated user — match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; } — and re-deploy firebase deploy --only firestore:rules.

  3. 允許CORS 生存到生產環境

    Symptom: a high-severity CORS misconfiguration finding. Cause: generated Express middleware: app.use(cors({ origin: '*' })). Fix: allowlist your frontend origin: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). For Next.js API routes, set Access-Control-Allow-Origin explicitly in the response.

  4. RLS 啟用但不強制

    Symptom: FixVibe reports that anonymous visitors can read a public table even though RLS looks enabled in the dashboard. Cause: RLS is on, but a policy such as USING (true) lets the anon role through, or the migration that tightened it never ran in production. Fix: replace the permissive policy with one scoped to auth.uid(), apply the migration, and re-scan.

  5. 未簽名IDOR-walkable ID

    Symptom: an active scan on your verified domain reports that one user can read another user's records at /api/items/1, /api/items/2, ... Cause: the API handler trusts the path param and queries Postgres without an ownership predicate. Fix: add .eq('user_id', user.id) on every read query, or move to signed URLs / UUIDs scoped under /api/users/[uid]/items/[id].

振動程式碼安全循環

我們的目標不是完美的安全性,而是完美的安全性。它消除了 AI 工具經常錯過的容易實現的目標,以便您可以保持快速交付。

  1. Generate fast — 使用Cursor、Claude Code、Lovable、Bolt。這就是重點。
  2. Audit immediately — 執行上面設定的 grep,檢查RLS,驗證CSP,檢查驗證邊界。
  3. Harden at deploy — 中間件、環境分離、CSP 隨機數、HSTS、僅伺服器驗證。
  4. Monitor — FixVibe 每天被動,每週在經過驗證的網域上活躍,Slack 的 Webhook,Unlimited 上的威脅偵測。
  5. 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。

// scan your app

別再讀了,去找你應用裡的漏洞。

Drop in a URL — FixVibe runs every passive check from this guide plus the rest of its 230+ passive checks, usually in under a minute. Free, no install, no card.

  • Free 層 — 每月 3 次掃描,無卡片。
  • 針對任何 URL 進行被動掃描 — 無需網域驗證。
  • 針對 Cursor、Claude Code、Lovable、Bolt、v0、Replit 進行了調整。
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
如何保護由 AI 撰寫工具建置的應用 · FixVibe