← Terug naar de Academy
Studio van Duijn · Training

Vibe Coding
Security Checklist

Voordat jij iets live zet: 12 checkpunten die elke beginnende vibe coder moet kennen. Met uitleg waarom het belangrijk is en hoe je het instelt.

12 secties Praktische how-to's Stack: GitHub + Vercel + Supabase + Stripe
Waarom bestaat deze checklist?
De meeste beveiligingsproblemen bij vibe coding zijn geen hackeraanvallen. Ze zijn per ongeluk. Een API-key die in GitHub belandt. Een database die voor iedereen leesbaar is. Iemand die je AI-endpoint onbeperkt aanroept en jij betaalt de rekening. Deze checklist haalt de meest voorkomende fouten er proactief uit.
Voortgang 0 van 56 afgevinkt
1
Secrets & API Keys
Nooit een key hardcoden, echt nooit
6 punten ✓ Compleet
Waarom dit zo kritiek is: Een API-key in je code is hetzelfde als je bankpas met pincode op de deurmat leggen. GitHub-bots scannen public repositories binnen seconden na een push op blootgestelde keys. Een OPENAI-key die gevonden wordt kan duizenden euro's kosten voordat je het doorhebt.
🚩 Rode vlag
const OPENAI_KEY = "sk-xxxxxxxxxxxxxxxx"
const SUPABASE_KEY = "eyJhbGciOi..."
✅ Hoe stel je het in?
  1. Maak een bestand .env aan in je project root
  2. Zet daarin: OPENAI_API_KEY=sk-jouwkey
  3. Voeg .env en .env.local toe aan .gitignore
  4. Gebruik in je code: process.env.OPENAI_API_KEY
  5. Voeg de variabelen in bij Vercel, via Project, Settings, Environment Variables
  • Geen API-key hardcoded in de code
    Zoek in je project op sk-, eyJ, pk_live. Als je iets vindt, verplaats het direct naar .env.
  • .env staat in .gitignore
    Open je .gitignore en controleer of .env erin staat. Anders: voeg het toe en run git rm --cached .env als het al getrackt werd.
  • .env.local staat in .gitignore
    Next.js gebruikt .env.local voor lokale overschrijvingen. Zorg dat beide varianten geblokkeerd zijn.
  • Geen API-keys in GitHub commits (check historie)
    Ga naar je repo, GitHub, Security, Secret Scanning. Of installeer gitleaks lokaal voor een grondige scan.
  • Alle secrets staan in Vercel Environment Variables
    Vercel, jouw project, Settings, Environment Variables. Kies welke variabelen voor Production, Preview en Development gelden.
  • Verschillende keys voor Development en Production
    Maak aparte Stripe-test vs -live keys, aparte Supabase projecten of aparte OpenAI keys per omgeving. Zo ga jij bij testen nooit per ongeluk productiedata aan.
2
GitHub
Je code-opslag goed beveiligd
6 punten✓ Compleet
Waarom dit belangrijk is: Een public repository betekent dat de hele wereld je code kan zien. Inclusief eventuele passwords, configuraties of kwetsbaarheden. GitHub's Secret Scanning is gratis en scant automatisch op bekende key-formaten, maar alleen als je het aanzet.
✅ Secret Scanning aanzetten
  1. Ga naar je GitHub repository
  2. Klik op Settings
  3. Scroll naar Security, dan Code security and analysis
  4. Zet Secret scanning aan
  5. Optioneel: zet ook Push protection aan, dat blokkeert een push als er een key in zit
  • Repository is Private (tenzij bewust open source)
    GitHub, jouw repo, Settings, Change repository visibility. Bij twijfel: private.
  • Geen database wachtwoorden in commits
    Doorzoek je commits: git log -p | grep -i "password". Als er iets gevonden wordt: verander het wachtwoord en verwijder het uit de git-historie.
  • Geen Supabase Service Role Key gepusht
    De Service Role Key geeft volledige toegang tot je database, ook zonder RLS. Deze mag nooit in je code. Zoek op service_role in je repo.
  • Geen Stripe Secret Key gepusht
    De Stripe Secret Key begint met sk_live_ of sk_test_. Zoek daar op in je codebase en commit-historie.
  • Geen MailerLite of andere API Keys gepusht
    Geldt voor alle third-party services: MailerLite, Resend, Postmark, etc. Gebruik altijd environment variables.
  • Branch protection aan voor main
    Settings, Branches, Add branch protection rule. Vink aan: "Require pull request reviews before merging." Zo push je nooit per ongeluk rechtstreeks naar productie.
3
Supabase
Row Level Security, hier gaat het het meest mis
5 punten✓ Compleet
Waarom dit zo kritiek is: Supabase maakt het makkelijk om snel een database op te zetten. Maar standaard zijn tabellen publiek toegankelijk als je RLS niet aanzet. Dat betekent dat iedereen met jouw Supabase URL alle klantdata kan opvragen, gewoon via de browser.
🚩 Rode vlag

Als een onbekende persoon via de browser jouw klantdata kan uitlezen. Test dit: open een incognitovenster en probeer data op te halen via de Supabase REST API. Lukt het? Dan staat RLS uit.

✅ Row Level Security aanzetten
  1. Ga naar Supabase Dashboard, jouw project, Table Editor
  2. Klik op de tabel, Edit Table
  3. Zet Enable Row Level Security (RLS) aan
  4. Ga daarna naar Authentication, Policies
  5. Voeg een policy toe: bijv. auth.uid() = user_id zodat gebruikers alleen hun eigen rijen zien
  6. Herhaal voor elke tabel met gevoelige data
  • Row Level Security (RLS) staat aan voor alle tabellen
    Supabase, Table Editor, klik op elke tabel, controleer of RLS enabled is. Zonder RLS is je database publiek.
  • Tabellen zijn niet publiek leesbaar zonder beleid
    RLS aan betekent nog niet dat er een policy is. Ga naar Authentication, Policies en zorg dat elke tabel minimaal één SELECT-policy heeft.
  • Tabellen zijn niet publiek schrijfbaar
    Voeg ook INSERT/UPDATE/DELETE policies toe. Zonder policy en met RLS aan: niets kan. Maar bij een "public" policy: iedereen kan alles.
  • Ingelogde gebruikers zien alleen hun eigen data
    Standaard policy: USING (auth.uid() = user_id). Dit zorgt dat user A nooit de data van user B ziet.
  • Service Role Key wordt alleen server-side gebruikt
    De Service Role Key mag NOOIT in frontend-code (React, Next.js client components). Alleen in API routes, server actions of Edge Functions. Nooit in de browser.
4
Stripe
Iemand kan jouw cursus kopen voor 1 cent als je dit verkeerd doet
4 punten✓ Compleet
Waarom dit belangrijk is: Als de prijs bepaald wordt door de frontend (wat de browser naar je server stuurt), kan iemand dat aanpassen. De checkout sessie moet altijd server-side aangemaakt worden met een vaste prijs uit jouw code, nooit een prijs die de gebruiker invoert of doorstuurt.
🚩 Rode vlag
// NOOIT: prijs komt van de gebruiker
const price = req.body.price
await stripe.checkout.sessions.create({ line_items: [{ price }] })
// GOED: prijs staat hardcoded in je server-code
const CURSUS_PRIJS = 'price_xxxYourStripeProductIDxxx'
await stripe.checkout.sessions.create({ line_items: [{ price: CURSUS_PRIJS, quantity: 1 }] })
✅ Webhooks instellen
  1. Stripe Dashboard, Developers, Webhooks, Add endpoint
  2. Vul jouw Vercel URL in: https://jouwnaam.vercel.app/api/webhook
  3. Selecteer events: checkout.session.completed, invoice.paid
  4. Kopieer de Webhook Signing Secret naar je .env als STRIPE_WEBHOOK_SECRET
  5. Verifieer elke webhook met stripe.webhooks.constructEvent()
  • Stripe Secret Key staat alleen server-side
    De key die begint met sk_live_ mag nooit in frontend-code. Gebruik de Publishable Key (pk_live_) voor de browser en de Secret Key alleen in API routes.
  • Webhooks zijn geconfigureerd en geverifieerd
    Zonder webhook-verificatie kan iemand een nep-betaling sturen naar jouw endpoint en toch toegang krijgen. Gebruik altijd stripe.webhooks.constructEvent().
  • Prijzen komen nooit van de frontend
    Gebruik altijd Stripe Price IDs die je in je Stripe Dashboard aanmaakt. Nooit een bedrag dat de gebruiker invoert of dat door de frontend wordt meegestuurd.
  • Checkout sessie wordt server-side aangemaakt
    De browser roept een API route aan, die API route maakt de Stripe sessie, de browser wordt doorgestuurd naar de Stripe betaalpagina. Nooit direct vanuit de browser.
5
Authenticatie
Wie mag waar komen?
5 punten✓ Compleet
Waarom dit belangrijk is: Authenticatie-fouten zijn de meest voorkomende oorzaak van datalekken. Een admin-dashboard dat gewoon bereikbaar is als je de URL weet. Sessies die nooit verlopen. Wachtwoord reset die niet werkt. Basisdingen die veel gebeuren bij snel gebouwde apps.
✅ De incognito-test

Open een incognitovenster. Probeer handmatig naar /admin, /dashboard, /account te navigeren. Word je doorgestuurd naar de loginpagina? Dan is het goed. Kom je er gewoon in? Dan is er een probleem.

  • Login verplicht waar nodig
    Voeg middleware toe (bijv. in Next.js: middleware.ts) die routes beschermt. Bij Supabase Auth: check altijd getUser() server-side.
  • Admin pagina's zijn afgeschermd op rol
    Het is niet genoeg om een link te verbergen. De route zelf moet controleren of de ingelogde gebruiker admin-rechten heeft. Voeg een role of is_admin kolom toe in Supabase.
  • Wachtwoord reset werkt correct
    Test dit zelf: vraag een wachtwoord reset aan met een bestaand e-mailadres. Komt de mail aan? Werkt de reset link? Wordt er ingelogd na reset? Allemaal testen.
  • Sessies verlopen automatisch
    Supabase Auth doet dit standaard (JWT expiry). Check dat je de sessie niet oneindig verlengt zonder re-authenticatie. Supabase vernieuwt tokens automatisch maar je kunt de duur instellen via Auth Settings.
  • Admin interface is niet zichtbaar voor gewone bezoekers
    Zowel qua navigatie (niet tonen) als qua routing (redirect als je probeert in te gaan). Dubbele bescherming: UI en server-side check.
6
Formulieren
Spam, SQL-injectie en foute invoer
4 punten✓ Compleet
Waarom dit belangrijk is: Formulieren zijn de voordeur van je applicatie voor aanvallers. SQL-injectie, XSS-aanvallen, spambots, allemaal komen ze via formulieren binnen. Het goede nieuws: moderne frameworks en Supabase beschermen je al voor het meeste. Maar je moet het wel goed gebruiken.
🚩 Rode vlag, SQL Injectie
// NOOIT zo: directe string interpolatie in SQL
const query = `SELECT * FROM users WHERE email='${email}'`
// GOED: gebruik Supabase client (parameterized queries)
const { data } = await supabase.from('users').select().eq('email', email)
  • Spam beveiliging aanwezig (honeypot of Turnstile)
    Voeg een honeypot-veld toe (verborgen input die mensen niet invullen maar bots wel) of gebruik Cloudflare Turnstile (gratis, privacy-vriendelijk alternatief voor reCAPTCHA).
  • Rate limiting ingesteld op formulier-endpoints
    Vercel heeft ingebouwde rate limiting via Edge Middleware. Vercel KV of Upstash Redis zijn populaire keuzes. Basisregel: max 5 submissions per minuut per IP.
  • Input validatie aan beide kanten (client en server)
    Frontend-validatie is voor UX. Server-side validatie is voor veiligheid. Gebruik Zod voor schema-validatie: z.string().email().max(255). Vertrouw nooit de input van de browser.
  • Geen raw SQL met user input (gebruik ORM of Supabase client)
    De Supabase JavaScript client gebruikt automatisch parameterized queries. Gebruik je Supabase goed? Dan ben je al beschermd. Gebruik je raw supabase.rpc() met user input? Dan moet je zelf sanitizen.
7
Bestandsuploads
Als je uploads toestaat, beveilig ze dan
4 punten✓ Compleet
Waarom dit belangrijk is: Als gebruikers bestanden kunnen uploaden en jij controleert het type niet, kan iemand een PHP-bestand of een EXE uploaden en uitvoeren op jouw server. Dit geldt ook voor Next.js apps: een ongelimiteerde bestandsupload kan je server crashen of je storage-kosten exploderen.
  • Alleen toegestane bestandstypes geaccepteerd
    Controleer het MIME-type server-side, niet alleen de extensie. Extensies zijn eenvoudig te vervalsen. Voorbeeld: if (!['image/jpeg','image/png','image/webp'].includes(file.type)).
  • Maximale bestandsgrootte ingesteld
    Stel een limiet in: bijv. max 5MB voor afbeeldingen. In Next.js: export const config = { api: { bodyParser: { sizeLimit: '5mb' } } }. Bij Supabase Storage: stel het in via bucket-instellingen.
  • Geen uitvoerbare bestanden toegestaan (.php, .exe, .sh)
    Blokkeer expliciet: .php, .exe, .sh, .py, .js als uploads. Een whitelist is veiliger dan een blacklist: sta alleen toe wat je nodig hebt.
  • Geüploade bestanden opgeslagen in Supabase Storage (niet public folder)
    Gebruik Supabase Storage buckets, niet de public-map van je Next.js project. Stel in of een bucket publiek of privé is. Voor profielfoto's: publiek. Voor documenten: privé met signed URLs.
8
AI Functionaliteiten
Specifiek relevant voor vibe coders
5 punten✓ Compleet
Waarom dit extra belangrijk is: AI API-aanroepen kosten geld per token. Als jouw endpoint niet beveiligd is, kan iemand het onbeperkt aanroepen en loop jij een rekening van honderden euro's op. Bovendien mag je onder de AVG geen persoonsgegevens naar OpenAI sturen zonder toestemming.
🚩 Rode vlag

Kan iemand jouw /api/chat endpoint onbeperkt aanroepen zonder in te loggen? Dat is een open rekening. Zet altijd minimaal authenticatie en rate limiting op AI-endpoints.

  • OpenAI/Anthropic API key blijft server-side
    Alle AI-aanroepen gaan via een API route (/api/chat, /api/generate). De key staat in je environment variables en nooit in client-code. Gebruik process.env.OPENAI_API_KEY.
  • Geen klantgegevens naar OpenAI gestuurd zonder toestemming
    Namen, e-mailadressen, adressen zijn persoonsgegevens. Vermeld in je privacyverklaring dat je AI gebruikt. Vraag toestemming voor verwerking of anonimiseer de data voor je het naar de API stuurt.
  • Prompts worden niet gelogd met persoonsgegevens
    Als je logging gebruikt voor debugging: zorg dat namen, e-mails en andere persoonsgegevens niet in de logs terechtkomen. Gebruik masking of log alleen de anonieme structuur.
  • Rate limits ingesteld op AI-endpoints
    Zonder rate limit kan één gebruiker (of bot) je honderden euro's kosten in één uur. Gebruik Upstash Rate Limiter of Vercel Edge Middleware. Stel in: max 10 requests per minuut per gebruiker.
  • Kostenlimiet ingesteld bij OpenAI/Anthropic
    OpenAI: Platform, Billing, Usage limits, stel een maandelijks hard limit in. Anthropic: idem via de console. Stel ook een soft limit in voor een waarschuwing. Dit is je vangnet als rate limiting faalt.
9
Vercel
Deployment-configuratie checken
5 punten✓ Compleet
Waarom dit belangrijk is: Vercel is verantwoordelijk voor je productie-omgeving. Een foutieve configuratie kan ervoor zorgen dat debug-informatie zichtbaar is, dat environment variables ontbreken of dat je domein niet correct gekoppeld is aan SSL.
  • Environment variables correct ingesteld voor Production
    Vercel, Project, Settings, Environment Variables. Controleer dat alle .env variabelen hier ook staan en dat ze op "Production" gezet zijn (niet alleen Preview).
  • Debug mode / verbose logging staat uit in productie
    Zorg dat NODE_ENV=production actief is. Vercel doet dit automatisch voor productie-deploys. Controleer dat je geen console.log(userData) of error-details naar de browser stuurt.
  • Productie domein is gekoppeld
    Vercel, Project, Settings, Domains. Voeg je eigen domein toe en verwijder de vercel.app URL als je wilt dat de canonical URL jouw domein is (goed voor SEO en beveiliging).
  • SSL actief (HTTPS) op alle domeinen
    Vercel regelt SSL automatisch via Let's Encrypt. Controleer of je domein groen is in Vercel, Domains. Is er een fout? Controleer je DNS-instellingen (CNAME of A-record naar Vercel).
  • Geen gevoelige data zichtbaar in Vercel Function Logs
    Vercel, Project, Functions, bekijk de logs. Zie je API-keys, wachtwoorden of persoonsgegevens voorbijkomen? Dan log je te veel. Verwijder gevoelige console.logs.
10
Dependency Check
Kwetsbaarheden in packages die je gebruikt
3 punten✓ Compleet
Waarom dit belangrijk is: Jouw code is maar zo veilig als de packages die je gebruikt. Een populaire NPM-package kan een kwetsbaarheid bevatten. npm audit scant je project op bekende problemen en is één commando.
✅ Uitvoeren in je terminal
npm audit
npm audit fix
npm update

Run dit in de map van je project. Bij "high" of "critical" meldingen: fix ze voor je live gaat. Bij "moderate": afwegen.

  • npm audit uitgevoerd, geen critical of high kwetsbaarheden
    Run npm audit in je projectmap. Zie je kritieke meldingen? Run eerst npm audit fix. Zijn er daarna nog issues? Zoek naar alternatieven voor die packages.
  • Kritieke dependencies zijn up-to-date
    Controleer package.json: zijn de versies van Next.js, Supabase client, Stripe en React recent? Gebruik npx npm-check-updates voor een overzicht.
  • Geen packages die al jaren niet onderhouden zijn
    Kijk op npmjs.com naar de "last published" datum van packages. Een package met de laatste update uit 2019 en geen maintenance-bericht is een risico. Zoek een alternatief.
11
Privacy & AVG
Verplicht als je data van EU-gebruikers verwerkt
5 punten✓ Compleet
Waarom dit extra belangrijk is voor een AI Academy: Je verwerkt e-mailadressen (MailerLite), betaalgegevens (Stripe) en mogelijk persoonsgegevens in AI-prompts. Dat zijn allemaal verwerkingen onder de AVG. Je hebt een privacyverklaring nodig en verwerkersovereenkomsten met je tools.
  • Privacyverklaring aanwezig en toegankelijk
    Zet een link naar je privacyverklaring in de footer en bij elk formulier. Vermeld welke data je verzamelt, waarvoor en met welke tools (Supabase, Stripe, MailerLite, OpenAI). Gratis generator: iubenda.com of cookieyes.com.
  • Cookie melding aanwezig (als je analytics of tracking gebruikt)
    Gebruik je Google Analytics, Meta Pixel, of andere tracking? Dan is een cookiebanner verplicht. Gebruik je alleen first-party analytics (Plausible, Vercel Analytics)? Dan is het simpeler. Geen tracking betekent geen banner nodig.
  • Verwerkersovereenkomst aanwezig voor MailerLite
    MailerLite: Log in, Settings, Data Privacy, Data Processing Agreement. Download en bewaar. Dit is verplicht als je EU-abonnees hebt.
  • Verwerkersovereenkomst aanwezig voor Stripe
    Stripe heeft een standaard Data Processing Agreement (DPA). Stripe Dashboard, Settings, Privacy & Legal, Data Processing Agreement. Accepteren en opslaan.
  • Contactformulier vermeldt hoe data verwerkt wordt
    Voeg bij elk formulier een kort zinnetje toe: "Je gegevens worden gebruikt om op je bericht te reageren en worden niet gedeeld met derden." Met een link naar de privacyverklaring.
12
Backups
Wat doe je als het misgaat?
4 punten✓ Compleet
Waarom dit belangrijk is: Een database die wordt verwijderd. Een deploy die de boel stukgooit. Een account dat gehackt wordt. Backups zijn niet spannend, maar het moment dat je ze nodig hebt, zijn ze het meest waardevolle wat je hebt.
✅ Supabase backup instellen
  1. Supabase Dashboard, Project, Settings, Backups
  2. Op de gratis tier: dagelijkse backups voor 7 dagen
  3. Op Pro tier: Point-in-Time Recovery (PITR) beschikbaar
  4. Test je backup: herstel hem eens op een test-project om te controleren dat het werkt
  • Supabase database backup is actief
    Supabase, Settings, Backups. Gratis plan heeft 7 dagen daily backups. Controleer dat ze actief zijn en noteer waar je ze kunt terugzetten.
  • Code staat in GitHub (nooit alleen lokaal)
    Push elke werkdag je wijzigingen naar GitHub. Stel desnoods een reminder in. Als je laptop crasht, is GitHub je backup van je hele project.
  • Stripe transactiedata is exporteerbaar
    Stripe Dashboard, Reports, Exports. Je kunt betalingen, klanten en facturen exporteren als CSV. Doe dit maandelijks als administratieback-up.
  • MailerLite abonneelijst is exporteerbaar
    MailerLite, Subscribers, Export. Exporteer je lijst maandelijks naar CSV en sla die op. Je mailinglijst is waardevoller dan je website, behandel hem zo ook.

De Britt-check: voordat je live gaat

Na de checklist: laat Claude en ChatGPT nog een ronde over je code gaan. Drie prompts, in deze volgorde.

Stap 1, ClaudeZoek actief naar beveiligingsproblemen in deze code. Kijk specifiek naar: blootgestelde API-keys, ontbrekende authenticatie, onbeveiligde endpoints, SQL-injectie risico's en RLS-problemen in Supabase. Geef per probleem aan hoe ernstig het is en hoe ik het oplos.
Stap 2, ChatGPTGedraag je als senior security engineer. Audit deze code op de OWASP Top 10 risico's. Mijn stack is: Next.js, Supabase, Stripe, Vercel, MailerLite. Geef een gestructureerd rapport met bevindingen per categorie.
Stap 3, ClaudeLos alle gevonden beveiligingsproblemen op uit de vorige audit. Laat me de aangepaste code zien en leg uit wat je veranderd hebt en waarom.

Die drie rondes halen gemiddeld 80 tot 90 procent van de beginnersfouten eruit. De rest is onderhoud.