FixVibe

// docs / security guides / hardening

Cara mengamankan aplikasi yang dibangun dengan alat coding AI

Panduan pengerasan langkah demi langkah untuk aplikasi yang Anda buat dengan Cursor, Claude Code, Lovable, Bolt, v0, Replit, atau Windsurf. Empat fase: pahami mengapa aplikasi yang dihasilkan AI- mengalami kegagalan secara berbeda, segera jalankan audit basis kode, lakukan penguatan pada waktu penerapan, lalu terus pantau. Berpendapat, naratif, dengan cuplikan nyata yang dapat Anda salin.

Mengapa aplikasi yang dihasilkan AI- gagal secara berbeda

Aplikasi berkode getaran bisa jadi aman. Mereka memerlukan izin audit ekstra karena mode kegagalannya bersifat struktural, bukan sembarangan:

  • 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).

Audit langsung: ambil basis kode Anda untuk mengetahui pola risiko

Sebelum Anda mengeraskan apa pun, temukan apa yang sudah rusak. Grep ini masing-masing membutuhkan waktu kurang dari satu menit:

Rahasia dan kunci penyedia

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

Setiap pukulan memerlukan penghapusan ditambah rotasi kunci. Dasbor Provider: Supabase โ†’ Pengaturan โ†’ API, Stripe โ†’ Pengembang โ†’ API kunci, konsol Antropik / OpenAI.

Kontrol akses basis data

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

Setiap CREATE TABLE public.* memerlukan ENABLE ROW LEVEL SECURITY yang cocok dan setidaknya satu kebijakan. Aturan Firestore harus membaca cakupan hingga request.auth.uid.

Penanganan autentikasi dan sesi

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/

Rute yang dirender server harus menggunakan supabase.auth.getUser() โ€” rute ini diverifikasi dengan backend. getSession() membaca cookie yang belum diverifikasi. Token di localStorage dapat diakses oleh skrip apa pun yang berjalan di halaman.

Header dan middleware

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/

Dengan tata letak src/, hanya src/middleware.ts yang diambil. Jika file middleware Anda berada di root proyek, Next.js mengabaikannya secara diam-diam dan logika CSP / auth-refresh Anda tidak pernah berjalan.

Pengerasan pada waktu penerapan

Setelah sumber bersih, kunci cara aplikasi mencapai produksi.

Langkah 1: Pisahkan lingkungan

Vercel: tiga lingkungan โ€” Production (domain produksi Anda), Pratinjau (PR / penerapan pementasan), Pengembangan (lokal). Masing-masing mendapatkan set env-varnya sendiri. Kunci live Stripe / Antropik / Supabase tidak pernah mencapai Pratinjau; Kunci pratinjau tidak pernah mencapai Production. Cabang mendorong ke Pratinjau secara otomatis; bergabung ke main disebarkan ke Production.

Langkah 2: Ketat CSP melalui middleware

Hasilkan nonce per permintaan, lalu masukkan ke Content-Security-Policy. Next.js menerapkan nonce secara otomatis ke tag skripnya sendiri ketika Anda menyetel header permintaan x-nonce.

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).*)'],
};

Langkah 3: Paksa RLS di setiap tabel publik

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);

Langkah 4: Verifikasi autentikasi khusus server pada setiap rute API

Setiap rute API yang mengubah status memverifikasi sisi server pemanggil dengan supabase.auth.getUser(). Objek pengguna menjadi sumber kebenaran untuk user_id โ€” jangan pernah mempercayai badan permintaan untuk menyetelnya.

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);
}

Langkah 5: Balikkan proxy analisis Anda

Promelakukan analisis melalui domain Anda sendiri untuk menghindari pemblokir iklan dan membuat CSP connect-src 'self' Anda tetap sempit. Pola yang sama berfungsi untuk PostHog, Masuk Akal, Umami, acara khusus tenggelam.

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(),
  });
}

Langkah 6: Penjaga pengalihan terbuka pada pantulan pasca-authentikasi

Alur proses masuk/pendaftaran biasanya menerima parameter kueri next. Tolak apa pun yang bukan jalur situs yang sama โ€” mulai dengan / dan jangan pernah // (protokol-relatif, mengirim pengguna ke luar situs).

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

Sedang berlangsung: pemantauan dan pemindaian ulang

Drift terjadi pada setiap penerapan. Perlakukan keamanan sebagai sebuah lingkaran, bukan daftar periksa yang Anda selesaikan.

Verifikasi domain produksi Anda

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.

Jadwalkan pemindaian ulang pasif

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"}'

Aktifkan API- pemindaian aktif (opsional)

Jika Anda ingin pemeriksaan aktif otomatis (SQLi / XSS / IDOR berjalan / dll.), aktifkan per domain di Dasbor โ†’ Domain โ†’ API aktif. Otorisasi tahan lama, masa berlakunya 90 hari, dan dapat langsung dibatalkan. Sandingkan dengan webhook scan.active_api.first_used sehingga pemindaian aktif otomatis pertama setelah pengaktifan mencapai peringatan Anda.

Masukkan temuan ke dalam alur kerja AI Anda

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.

Deteksi ancaman langsung (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.

Pola kegagalan nyata dan perbaikannya

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

  1. Kunci peran layanan dalam komponen klien

    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. Aturan Firestore dibiarkan dalam mode uji coba

    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. Permisif CORS bertahan hingga produksi

    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 diaktifkan tetapi tidak dipaksa

    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. ID yang tidak dapat ditandatangani IDOR-walkable

    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].

Lingkaran keamanan kode getaran

Tujuannya bukanlah keamanan yang sempurna; ini menghilangkan hal-hal yang selalu terlewatkan oleh alat AI sehingga Anda dapat terus melakukan pengiriman dengan cepat.

  1. Generate fast โ€” gunakan Cursor, Claude Code, Lovable, Bolt. Itulah intinya.
  2. Audit immediately โ€” jalankan set grep di atas, periksa RLS, verifikasi CSP, tinjau batas autentikasi.
  3. Harden at deploy โ€” middleware, pemisahan lingkungan, CSP nonce, HSTS, verifikasi autentikasi khusus server.
  4. Monitor โ€” FixVibe pasif harian, aktif mingguan pada domain terverifikasi, webhook ke Slack, deteksi ancaman di 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.

Langkah selanjutnya

Untuk latar belakang konseptual tentang DAST vs SAST dan mengapa aplikasi yang dihasilkan AI- memerlukan pemindaiannya sendiri, baca AI-generated code security scanning. Untuk referensi cepat audit pra-pengiriman, lihat vibe coding security checklist.

// scan your app

Cukup membaca. Saatnya temukan celah di aplikasimu.

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 tingkat โ€” 3 pemindaian / bulan, tanpa kartu.
  • Pemindaian pasif terhadap URL apa pun โ€” tidak memerlukan verifikasi domain.
  • Disetel untuk Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
Cara mengamankan aplikasi yang dibangun dengan alat coding AI ยท FixVibe