// 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 SECURITYnever 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
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 stringsSetiap pukulan memerlukan penghapusan ditambah rotasi kunci. Dasbor Provider: Supabase โ Pengaturan โ API, Stripe โ Pengembang โ API kunci, konsol Antropik / OpenAI.
Kontrol akses basis data
# 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;` matchesSetiap 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
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
# 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.
// 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.
-- 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.
// 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.
// 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).
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.
# 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:
- 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 tosrc/lib/supabase/service.tswithimport 'server-only'at the top; create a parallelsrc/lib/supabase/client.tsusing the anon key for client-side use; rotate the service-role key via Supabase Studio. - 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-deployfirebase deploy --only firestore:rules. - 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, setAccess-Control-Allow-Originexplicitly in the response. - 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 theanonrole through, or the migration that tightened it never ran in production. Fix: replace the permissive policy with one scoped toauth.uid(), apply the migration, and re-scan. - 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.
- Generate fast โ gunakan Cursor, Claude Code, Lovable, Bolt. Itulah intinya.
- Audit immediately โ jalankan set grep di atas, periksa RLS, verifikasi CSP, tinjau batas autentikasi.
- Harden at deploy โ middleware, pemisahan lingkungan, CSP nonce, HSTS, verifikasi autentikasi khusus server.
- Monitor โ FixVibe pasif harian, aktif mingguan pada domain terverifikasi, webhook ke Slack, deteksi ancaman di 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.
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.
