Security Checklist
Defense-in-depth security — controleer dit voor elk deploy.
Authenticatie & Autorisatie
- Supabase Auth — Email + magic link (geen wachtwoord opslaan)
- JWT Tokens — Korte levensduur (1u access, 7d refresh)
- Row Level Security — Op alle tabellen ingeschakeld
- Cirkel RLS — Alleen leden kunnen inhoud zien
- Notitie RLS — Schrijver → metadata, Ontvanger → inhoud NA opening
- Mentor-only — Uitnodigen, datum wijzigen, cirkel verwijderen
Testen:
# 1. Zorg dat RLS ingeschakeld is
SELECT tablename FROM pg_tables
WHERE schemaname='public';
# 2. Check policies bestaan
SELECT * FROM pg_policies;
# 3. Test RLS
-- Login als user A
SELECT * FROM cirkels; -- Moet empty zijn (geen lid)
-- Voeg user A toe als lid
INSERT INTO lidmaatschappen VALUES (...);
SELECT * FROM cirkels; -- Moet cirkel tonen
Encryptie
- HTTPS Everywhere — Vercel + Supabase (TLS 1.3)
- Notities AES-256-GCM — Client-side encryptie
- Cirkel-sleutel — Opgeslagen in Supabase Vault (encrypted at rest)
- IV Uniek — Elke notitie heeft random IV (12 bytes)
- Server Ziet Nooit Plaintext — Alleen ciphertext + IV
- Sleutel Timing — RLS blokkeert sleutel tot opening_datum
Testen:
// 1. Encryptie-decryptie roundtrip
const tekst = "Geheim";
const sleutel = await genereerCirkelSleutel();
const { versleuteld, iv } = await versleutelNotitie(tekst, sleutel);
const decrypted = await ontsleutelNotitie(versleuteld, iv, sleutel);
console.assert(decrypted === tekst, "FAIL: Encryptie niet werkend");
// 2. Server kan niet decrypten
// (dump DB: versleutelde_tekst mag niet leesbaar zijn)
// 3. RLS blokkeert sleutel tot opening
SELECT cirkel_sleutel FROM cirkels WHERE opening_datum > NOW();
// Moet NULL zijn
Privacy & GDPR/AVG
- Expliciete Opt-In WhatsApp — Apart checkbox (niet standaard aan)
- Privacyverklaring — Vereist bij registratie
- Data Minimalisatie — Alleen e-mail, naam, 06 opgeslagen
- Recht op Verwijdering — Zelfservice + automatisch na 30d
- Audit Trail Geanonimiseerd — Geen email opgeslagen, alleen hash
- Geen Tracking Pixels — Emails zijn plain-text compatible
- Nederlandse Verwerkersovereenkomst — Supabase EU-only
Testen:
# 1. Audit log niet identificeerbaar
SELECT * FROM verwijderlogs
WHERE actie = 'account_vernietigd';
-- email_hash moet onleesbare hash zijn (SHA-256)
-- voornaam_initials mag niet full name bevatten
# 2. Automatische verwijdering na 30d
SELECT COUNT(*) FROM profielen
WHERE verwijder_op < NOW() AND verwijder_op IS NOT NULL;
-- Moet 0 zijn (cron moet hebben gerund)
# 3. Privacy policy zichtbaar
curl https://liefdevolleblik.nl/privacy
# Moet volledig privacybeleid tonen
Rate Limiting & Misbruik
- API Rate Limiting — 10 req/min per IP (Vercel middleware)
- Max Leden per Cirkel — 50 (database constraint)
- Max 1 Notitie per Schrijver per Ontvanger — (unique constraint)
- Tokens Éénmalig — Uitnodigingstokens kunnen maar 1x gebruikt worden
- Token Expiry — 7 dagen (auto-verwijderd)
- CAPTCHA op Signup — hCaptcha (niet Google reCAPTCHA)
Testen:
# 1. Rate limiting
for i in {1..15}; do
curl -H "X-Forwarded-For: 1.2.3.4" https://liefdevolleblik.nl/api/...
done
# Na 10 requests: 429 Too Many Requests
# 2. hCaptcha aanwezig
curl https://liefdevolleblik.nl/registreer
# HTML moet hCaptcha script bevatten
# 3. Token éénmalig
# Gebruik invitation token 2x
# 1e keer: succesvol
# 2e keer: 400 Bad Request
Infrastructure & Secrets
- Environment Variables — Nooit in code (
.envin .gitignore) - Service Role Key — Alleen in server-side cron (Edge Functions)
- Vault Encrypted Keys — Cirkel-sleutel encrypted at rest
- CSP Headers — Content-Security-Policy ingesteld
- CORS — Alleen liefdevolleblik.nl allowed
- Vercel Secrets — Edge Functions haben aparte secrets
Testen:
# 1. Secrets niet in git
git log -p --all --grep="SUPABASE_KEY"
# Moet niks returnen
git log -S "eyJ" --oneline
# Moet niks returnen (base64 JWT)
# 2. CSP headers aanwezig
curl -I https://liefdevolleblik.nl
# Content-Security-Policy: default-src 'self' ...
# 3. CORS check
curl -H "Origin: evil.com" \
-H "Access-Control-Request-Method: POST" \
https://liefdevolleblik.nl/api/notitie
# Mag GEEN Access-Control-Allow-Origin teruggeven
Logging & Monitoring
- Geen Notitie-Inhoud Gelogd — (Vervel moet dit checken)
- Error Tracking — Sentry of equivalent
- Performance Monitoring — Datadog / Cloudflare Analytics
- Audit Logging — Alle data-vernietiging gelogd
- Alert op Fout — Cron jobs sturen alert bij failure
Testen:
# 1. Logs bevatten geen notitie-tekst
grep -r "notitie" /path/to/logs
# Mag niet "Geheim briefje" bevatten
# 2. Sentry verbonden
curl https://liefdevolleblik.nl/api/error-test
# Error moet in Sentry verschijnen
# 3. Cron logs beschikbaar
supabase functions logs dagelijkse-check
# Moet logs tonen (success of error)
Database Security
- RLS Policies — Op alle tabellen
- Foreign Keys — CASCADE delete correct
- Constraints — Telefoon-format check, unique emails
- Indexes — Performance optimizations
- Encryption at Rest — Supabase default
- Backups — Automatisch (Supabase)
Testen:
-- 1. RLS ingeschakeld
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname='public';
-- Alle moeten 't' zijn voor rowsecurity
-- 2. Constraints
SELECT constraint_name, constraint_type
FROM information_schema.table_constraints
WHERE table_name='profielen';
-- 3. Backups
-- Supabase Dashboard → Database → Backups
-- Moet auto-backups tonen
API Security
- Input Validation — Zod schemas op alle routes
- SQL Injection Prevention — Parameterized queries (Supabase)
- XSS Prevention — React auto-escapes, no innerHTML
- CSRF Protection — SameSite=Strict cookies
- Middleware Auth — Protected routes checks token
Testen:
// 1. Input validation
const response = await fetch('/api/cirkel', {
method: 'POST',
body: JSON.stringify({
naam: '"; DROP TABLE cirkels; --',
opening_datum: 'invalid'
})
});
// Moet 400 Bad Request (Zod validation)
// 2. XSS
const response = await fetch('/api/cirkel', {
method: 'POST',
body: JSON.stringify({
naam: '<script>alert("xss")</script>',
opening_datum: '2025-12-31'
})
});
// Naam wordt escaped in HTML
Third-Party Security
- WhatsApp API — Valideer messages, rate limit
- Resend Email — DKIM/SPF/DMARC records
- hCaptcha — Verify token server-side
- Supabase Auth — Magic link validation
Testen:
# 1. DKIM/SPF check
mxtoolbox.com
# liefdevolleblik.nl moet DKIM/SPF pass
# 2. Email deliverability
# Send test email from Resend
# Check spam folder (moet in inbox zijn)
# 3. hCaptcha verification
# Signup met hCaptcha
# Check backend log: verification success
Pre-Launch Checklist (48 uur voor launch)
- Alle secrets in Vercel (niet in git)
- RLS policies tested
- Encryption tested (roundtrip)
- Rate limiting tested
- CAPTCHA working
- WhatsApp messages tested
- Email templates tested
- Cron jobs scheduled
- Database backups enabled
- Error tracking enabled
- Security headers enabled
- HTTPS working
- Privacy policy live
- Terms of Service live (if required)
Regular Security Audits
Maandelijks
- Check Supabase security advisories
- Review audit logs
- Check failed auth attempts
- Verify backups are recent
Kwartaals
- Dependency updates (npm audit)
- Penetration testing (if budget)
- Data breach simulation
Jaarlijks
- Full security audit
- GDPR compliance review
- Disaster recovery drill
Incident Response
Wat te doen bij security incident:
- Discover: Bug report → confirm exploit
- Contain: Disable affected feature (if critical)
- Investigate: Check logs, database
- Fix: Patch code, update database
- Communicate: Email users (transparency)
- Post-Mortem: Learn & document
Incident Contacts:
- Supabase Support: support@supabase.io
- Vercel Support: support@vercel.com
- Security report: security@liefdevolleblik.nl
Vragen over security? Open issue op GitHub met label security.