07 — Sécurité (avant chaque livraison : /security-pass)
« Je n’ai pas envie qu’un client se fasse hacker son site par ma faute. » Mindset : le 100 % n’existe pas. La question n’est pas « être inviolable » mais réduire la surface d’attaque + limiter le rayon d’explosion (defense in depth). Une vitrine statique est déjà très sûre (pas de serveur/DB) → version allégée. Dès qu’il y a backend / DB / auth / formulaires → version sérieuse.
0. Sécurité du repo (GitHub) — surface souvent négligée
« Privé donc safe » = faux : si un repo privé fuite, c’est tout l’historique qui est exposé.
- Compte : 2FA obligatoire, méthode forte (passkey/TOTP), jamais SMS (SIM swap). En org, imposer la 2FA au niveau org.
- Accès = moindre privilège : freelance/client = accès au repo concerné seulement, retiré dès la fin de mission (accès orphelin = faille). Gérer par teams en org.
- Secrets dans l’historique : activer secret scanning + push protection +
gitleaks/trufflehogen local. Si fuite : révoquer + régénérer D’ABORD (c’est ça qui protège), PUIS nettoyer l’historique. L’ordre compte. - Branch protection
main: bloquer le force-push (intégrité, compatible avec le push-direct solo). Require PR + review = surtout en équipe. ⚠️ Indisponible sur repo privé en plan gratuit (403 « Upgrade to Pro »). - Tokens : fine-grained, perms minimales, expiration 30-90 j. Déploiement : deploy keys read-only si suffisant.
- GitHub Actions : pinner les actions tierces à un SHA,
permissions:minimal surGITHUB_TOKEN, méfiancepull_request_target, jamais logger un secret. - Hygiène :
.gitignorecorrect dès l’init. Bonus : signature des commits.
1. Secrets — LE piège n°1 du code généré par IA
- Aucun secret en dur, jamais (clés API, token Sanity, service-role Supabase, SMTP) → variables d’environnement.
.env*dans.gitignoreAVANT le premier commit. Puis scanner l’historique — une clé committée puis « supprimée » reste lisible dans le git history.- Une clé qui a fui se RÉVOQUE et se régénère, pas juste « retirée du code ».
- Client vs serveur : sur Next, tout
NEXT_PUBLIC_*part dans le bundle = public. Service-role Supabase / token d’écriture Sanity = serveur only, jamais dans un"use client".
2. Données & accès (si backend)
- Supabase = RLS sur chaque table, policies explicites par opération. + policy RESTRICTIVE
require_aal2si MFA. Sans RLS, l’anon key (publique) donne accès à tout. - Autorisation TOUJOURS côté serveur. Un
if (user.isAdmin)en React se contourne en 2 s. Vérif serveur-side (server action / RLS), surtout pour les actions en service-role qui bypassent la RLS. - Formulaires publics : rate limiting + anti-bot (Turnstile Cloudflare / hCaptcha) contre spam/abus.
3. Le code
- Validation des entrées côté serveur (form, query, payload) typée + validée, idéalement Zod. Ne jamais faire confiance au navigateur.
- XSS : attention à
dangerouslySetInnerHTML; pour le rich text (Portable Text Sanity), passer par un sanitizer (DOMPurify) ou un renderer sûr. - Injection : dès qu’on écrit du SQL brut / une RPC, paramétrer, jamais concaténer de chaîne user.
4. Transport & headers
- HTTPS partout (auto sur Cloudflare/Vercel ; sur o2switch, vérifier certif + redirection http→https).
- Security headers : CSP + HSTS +
X-Content-Type-Options: nosniff+Referrer-Policy+Permissions-Policy. Tester sur securityheaders.com + observatory.mozilla.org. (Hors kit auto : application guidée, une CSP mal réglée peut casser le site.)
5. RGPD (clients FR)
- Minimisation, bandeau de consentement réel si cookies/analytics non essentiels, mentions légales + politique de confidentialité, durée de conservation définie.
- Hébergement EU : Cloudflare/Vercel régions EU ; Supabase = région EU à la création (Frankfurt/Paris, pas us-east).
- Analytics par défaut Cloudflare Web Analytics (sans cookie → pas de bandeau requis). →
05.
6. Dans la durée
npm audit régulier (patcher le transitif via overrides, jamais audit fix --force à l’aveugle). Dependabot activé : (a) alerts + security updates, (b) version-updates via .github/dependabot.yml. Surveiller les logs.
Kit de durcissement Songe (déposé auto par /security-pass)
Fichiers communs à tous les repos (autonomes, additifs, idempotents) ; templates dans ~/.claude/templates/ :
dependabot.yml→.github/si absent.gitignore-baseline→ garantir que.env*est ignoré.- Hors kit (jamais auto) : security headers (fusion + CSP délicate) et branch protection (réglage API) → restent en report-only/guidé.
Vérifs EMPIRIQUES (exécuter, pas supposer)
- Lire chaque table avec la clé anon → 0 ligne attendu.
grepdu client service-role / token dans les"use client"→ aucun ; scan history (gitleaks).npm audit --omit=dev. Note des headers sur securityheaders.com.
Staging non indexable jusqu’à la prod
- Baseline noindex gated :
robots:{index:false,follow:false,…}dansmetadatadu layout, actif saufNEXT_PUBLIC_INDEX==="on". Vérif :curl -s URL | grep 'name="robots"'. - Mur d’auth = seulement si données réelles ou si demandé « privé/fermé » (un outil interne muré doit AUSSI être noindex, sinon sa page
/loginreste indexable). - Jour de la livraison seulement :
NEXT_PUBLIC_INDEX=on+ retirer la protection éventuelle + brancher le vrai domaine.