BlockLucky Documentation officielle GitHub

Audits et checklists

Deux audits par checklist ont été menés pendant le projet : un audit d'infrastructure d'environ 245 points et un audit de sécurité d'environ 300 points. Cette page présente leur structure, explique ce que leurs fichiers de travail permettent d'établir et donne, thème par thème, l'état du dépôt vérifié dans le code.

Ce que les fichiers d'audit permettent d'établir

Important

Les deux checklists sont des grilles de référence. Dans leurs exports de travail, la colonne Statut de chaque ligne vaut « À faire » et l'onglet de synthèse de la checklist de sécurité affiche zéro point « Fait ». Les grilles ont servi de liste de contrôle pendant le durcissement du dépôt, mais leur suivi n'a pas été reporté dans les fichiers. Ces fichiers ne permettent donc pas d'établir un taux de conformité, et cette page n'en donne aucun. La synthèse ci-dessous a été établie en vérifiant chaque thème directement dans le dépôt ; chaque mesure déclarée faite renvoie à sa preuve.

Checklist d'infrastructure

Chaque ligne décrit un anti-modèle, la règle à appliquer, les outils de référence et une priorité (Haute, Moyenne, Basse). Les 245 points se répartissent en huit thèmes :

Thème Points
Architecture et organisation du code 35
Base de données (PostgreSQL) 35
Cache et asynchronisme (Redis) 34
Performance et passage à l'échelle 31
Expérience développeur et tests 30
CI/CD et déploiement 28
Observabilité et supervision 28
Haute disponibilité et résilience 24

Checklist de sécurité

Chaque ligne donne une mesure, la règle ou la norme de référence (OWASP ASVS et MASVS, ANSSI, CNIL), une priorité, la plateforme concernée et des colonnes de suivi (Statut, Responsable, Date cible, Preuve). La grille rappelle qu'un point « Fait » doit être prouvé par un test, une configuration ou un rapport, et que ses points juridiques ne remplacent pas l'avis d'un avocat ou d'un DPO.

Priorité Signification Points
P0 Indispensable avant toute mise en ligne 123
P1 Avant le lancement public 118
P2 Avec la croissance 48
P3 Applications très sensibles (banque, santé) 11

Les 300 points sont répartis en 22 catégories. Elles structurent la synthèse ci-dessous.

Synthèse de l'audit de sécurité

Légende : fait (preuve dans le dépôt), partiel (mesure présente mais incomplète), absent, sans objet (fonction inexistante dans BlockLucky).

Authentification (18 points) et authentification avancée (12 points)

Mesure État Preuve ou commentaire
Hachage et politique de mot de passe fait bcrypt coût 12, 10 à 128 caractères avec une lettre et un chiffre, backend/src/api/auth/auth.validators.ts
Mots de passe compromis absent Aucune vérification contre une liste de fuites
Réinitialisation du mot de passe fait Code email à usage unique, réponse identique pour une adresse inconnue, révocation de toutes les sessions, alerte par email
Connexion par fournisseur d'identité fait Jetons Google vérifiés côté serveur (signature, émetteur, audience, email vérifié), backend/src/services/oauth-verifier.service.ts ; rattachement par adresse email
Limitation des tentatives fait 30 requêtes par IP en 10 minutes, 10 échecs par compte ou par adresse inconnue en une heure, mots de passe compris, backend/src/services/otp-challenge.service.ts
TOTP conforme RFC 6238, secret de 160 bits chiffré, anti-rejeu fait backend/src/services/totp.service.ts, test d'anti-rejeu
Codes de récupération hachés, usage unique fait Authentification
MFA obligatoire pour les administrateurs partiel Imposée par requireRoles ; le code TOTP exige désormais un premier facteur (ticket de connexion), mais ce premier facteur peut être un code email, sans mot de passe ; voir Limites
Comparaison en temps constant fait safeEqual
Notifications de sécurité fait Modèle security-alert
Vérification de l'email avant création du compte fait Test exige le code envoyé par email...
Ré-authentification avant action sensible partiel Désactivation de la 2FA seulement
Anti-énumération partiel Message unique et temps égalisé sur /signin, réponse identique de /password/forgot ; 409 de /signup pour une adresse inscrite
Passkeys et WebAuthn, MFA résistant au hameçonnage absent
CAPTCHA, détection de connexions suspectes absent

Sessions et jetons (13 points), sessions et cryptographie avancées (10 points)

Mesure État Preuve ou commentaire
Validation stricte des JWT, durée limitée fait backend/src/services/session.service.ts
Déconnexion effective, révocation de tous les appareils fait Sessions
Pas de jeton dans l'URL partiel Jeton de session jamais dans l'URL ; le jeton de suppression RGPD figure dans le lien de l'email
Cookies HttpOnly, Secure, SameSite, préfixe __Host- sans objet Pas de cookie ; jeton dans localStorage sur le web
Stockage du jeton côté web partiel localStorage, exposé en cas de XSS
Rotation des jetons de rafraîchissement sans objet Pas de jeton de rafraîchissement
Liste des sessions actives absent
Rotation des clés de signature, clés par utilisateur, crypto-shredding absent

Autorisation et contrôle d'accès (12 points)

Mesure État Preuve ou commentaire
Autorisation côté serveur sur chaque route, refus par défaut fait Matrice des droits
Protection IDOR fait assertPartnerAccess, routes utilisateur sans identifiant en paramètre
Anti-affectation de masse, anti-escalade fait Schémas Zod, aucune route ne modifie un rôle
Back-office protégé fait router.use(authenticate, requireRoles('ADMIN'))
Identifiants non prédictibles fait UUID v7
Revue périodique des accès absent

Injections et validation des entrées (18 points)

Mesure État Preuve ou commentaire
Requêtes paramétrées fait Prisma uniquement
Validation par schéma, listes blanches fait Validation des entrées
Échappement des sorties (emails, CSV) fait escapeHtml, neutralizeSpreadsheetCell
Commandes système, XXE, SSTI, désérialisation sans objet Aucune de ces fonctions n'existe
Validation des paramètres de requête et des événements temps réel partiel limit non borné sur le classement, événements de chat non validés

Sécurité navigateur (14 points)

Mesure État Preuve ou commentaire
CSP partiel Stricte sur le site de l'Office de Tourisme ; 'unsafe-inline' dans script-src sur le web, absente en développement
HSTS, nosniff, anti-clickjacking, Referrer-Policy, Permissions-Policy, COOP fait frontend-web/next.config.mjs, frontend-office-tourisme/docker/security-headers.conf
CSRF sans objet Jeton en en-tête Authorization
CORS restrictif fait Liste d'origines explicite
Aucun secret ni source map dans le front fait Contrôle de CI du job web
Subresource Integrity sans objet Aucune ressource tierce chargée

Chiffrement et secrets (13 points)

Mesure État Preuve ou commentaire
Chiffrement applicatif des champs sensibles fait AES-256-GCM, Cryptographie et secrets
Aléa cryptographique fait crypto.randomInt, crypto.randomBytes
Aucun secret dans Git fait gitleaks
Secrets par environnement, refus des valeurs de développement partiel Quatre secrets contrôlés ; identifiants S3 et SMTP non contrôlés
Rotation des secrets, gestionnaire de secrets, KMS absent Aucun script de re-chiffrement
TLS, certificats hors dépôt Fournis par le proxy de déploiement

API et réseau (15 points)

Mesure État Preuve ou commentaire
Limitation de débit générale et sur l'authentification fait Protection des API
Webhooks entrants signés fait HMAC, fenêtre de 5 minutes, anti-rejeu
Idempotence partiel Côté serveur sur quatre routes ; les clients n'envoient pas l'en-tête
Erreurs sans fuite, en-têtes de version masqués fait Gestionnaire d'erreurs, helmet, poweredByHeader: false, server_tokens off
Délais d'expiration fait backend/src/server.ts, statement_timeout
Pagination obligatoire partiel Plafond de 100 pour l'historique, pas pour le classement
Protection DDoS, WAF absent
Concurrence (race conditions) partiel Contraintes d'unicité en base ; validation d'un code promo non atomique

Fichiers et téléversements (10 points)

Sans objet pour l'essentiel : l'API n'accepte aucun fichier. Les codes promo arrivent sous forme de texte CSV borné à 200 000 caractères, et les exports sont écrits par le serveur dans un compartiment S3.

Infrastructure et chaîne logicielle (18 points), chaîne logicielle et développement (10 points)

Mesure État Preuve ou commentaire
Mises à jour, scan de vulnérabilités, lockfiles fait Dependabot, npm audit, Trivy, npm ci
SAST fait CodeQL, Slither
Divulgation responsable fait SECURITY.md, security.txt
Base non exposée fait Ports liés à 127.0.0.1 dans docker-compose.yml
Conteneurs durcis fait read_only, cap_drop: [ALL], no-new-privileges, utilisateurs non root
Sécurité de la CI fait Actions épinglées par SHA, permissions minimales ; voir Chaîne logicielle
Délai d'adoption des nouvelles versions fait cooldown de 5 jours
DAST, test d'intrusion, SBOM, builds signés absent
Protection des branches, accès serveurs, DNS hors dépôt Non vérifiable dans le code

Journalisation et incidents (11 points)

Mesure État Preuve ou commentaire
Données interdites absentes des journaux fait Emails masqués, aucun code ni secret journalisé
Journal des actions d'administration partiel Disjoncteur, trésorerie, statut des partenaires, exports, consultation des pièces d'identité et décisions sur l'âge ; pas le tirage ni l'annulation manuels
Plan de réponse aux incidents, notification CNIL fait (documentaire) Réponse aux incidents
Coupe-circuit fait MAINTENANCE_MODE, disjoncteur du contrat
Centralisation, conservation et alertes absent Journaux sur la sortie standard, purge à mettre en place

Mobile (15 points) et mobile avancé (8 points)

Mesure État Preuve ou commentaire
Stockage sécurisé des secrets fait expo-secure-store, WHEN_UNLOCKED_THIS_DEVICE_ONLY
Pas de secret dans le binaire fait Variables EXPO_PUBLIC_* limitées à des URL et à des identifiants client publics
Autorisations minimales fait Appareil photo et photos seulement, demandées au moment de l'envoi de la pièce d'identité, avec un texte d'explication
Sauvegarde système exclue fait allowBackup: false
Épinglage de certificat, détection de root, attestation, masquage d'écran, mise à jour forcée absent Navigateur et mobile

Logique métier et fraude (15 points)

Mesure État Preuve ou commentaire
Montants calculés côté serveur et contrat fait Prix vérifié par le contrat (IncorrectPayment)
Bornes des valeurs métier fait Un ticket par participant et par grand tirage (schéma, service et maxTicketsPerPlayer du contrat, 1 par défaut), coût du ticket à gratter fixé par le serveur ; l'ancien plafond personnel de jeu responsable a été retiré, devenu sans objet
Contrôle d'âge partiel Pièce d'identité examinée par la Mairie, statut relu en base à chaque achat (Autorisations) ; pas de contrôle de correspondance entre la personne et la pièce
Machine à états fait RoundStatus du contrat
Anti-abus du parrainage partiel Points de parrainage attribués seulement après vérification de l'email ; pas de contrôle de comptes multiples
Tests automatisés des autorisations fait backend/__tests__/api/auth-security.test.ts
Double validation des actions critiques absent Une seule personne ADMIN suffit pour le tirage, le disjoncteur et la trésorerie

RGPD et vie privée (23 points), juridique et conformité (20 points), gouvernance des données (10 points)

Ces catégories sont traitées dans la partie Juridique et conformité. En résumé : registre des traitements, export et anonymisation sont faits ; purges automatiques, analyse d'impact, contrats de sous-traitance et mentions légales complètes sont absents ou à valider. La liste à faire valider figure dans Points à valider.

Architecture et API avancées (10 points), infrastructure et cloud avancés (15 points), organisation (10 points)

Ces catégories visent un hébergement en nuage et une organisation d'entreprise. Le dépôt ne contient ni infrastructure en code, ni compte cloud, ni processus d'équipe formalisé : elles relèvent du déploiement, décrit dans Déploiement sur etherbay.tech, et ne sont pas évaluables ici. Seule la validation de l'en-tête Host est concernée par le code : elle est absente (voir Validation).

Synthèse de l'audit d'infrastructure

Thème Fait, avec preuve Absent ou partiel
Architecture Monolithe modulaire par domaine (ADR 0001), TypeScript strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes dans backend/tsconfig.json Pas de règle de lint sur les frontières entre modules, pas de table outbox
Base de données Migrations expand et contract (ADR 0003), contrôle de dérive du schéma en CI, délai par requête, pool borné, pg_stat_statements Pas de réplique en lecture
Redis Repli mémoire (ADR 0002), délai de 500 ms par commande, dispersion des durées de cache, préfixe par environnement Le repli affaiblit la révocation et la limitation de débit
Files Tentatives bornées avec recul exponentiel et conservation des échecs dans backend/src/queues/queue.options.ts Pas d'alerte sur les tâches en échec
Observabilité Métriques Prometheus protégées, identifiant de requête, journaux structurés (Observabilité) Pas de centralisation des journaux
Haute disponibilité Sondes live, ready, deep, arrêt gracieux sur SIGTERM, RPC de secours, worker séparé Instance unique, pas de bascule automatique de base
CI/CD Filtre par projet modifié, cache, construction des images, intégration sur une vraie chaîne Pas de déploiement automatisé dans la CI
Performance et tests Scénarios k6 versionnés avec seuils (Tests de charge), tests unitaires, d'intégration et sur chaîne Tests de charge non exécutés en CI

Constats ouverts

Les écarts relevés pendant la rédaction de cette documentation, classés par priorité suggérée :

Priorité Constat Page
Haute Le propriétaire VRF hérité (déployeur) peut changer le coordinateur et donc le gagnant Clés et garde des portefeuilles
Haute cancelRound permet à l'opérateur de censurer un tirage en devançant la réponse VRF Modèle de menaces
Haute Aucun outil de rotation de ENCRYPTION_KEY Cryptographie et secrets
Moyenne La 2FA par email n'ajoute pas de facteur, et la connexion par code email reste ouverte aux comptes à mot de passe. Le code TOTP présenté avec le seul userId est refusé depuis l'introduction du ticket de connexion Authentification
Moyenne Rattachement d'une connexion Google à un compte existant par la seule adresse email Authentification
Moyenne Pièce d'identité en attente conservée sans limite de durée et lisible par tout ADMIN Autorisations
Moyenne withdrawFees du backend suppose que l'opérateur détient DEFAULT_ADMIN_ROLE Clés et garde des portefeuilles
Moyenne 'unsafe-inline' dans script-src alors que le jeton est dans localStorage Navigateur et mobile
Moyenne Le lien de confirmation RGPD de l'email pointe vers une page web inexistante RGPD
Basse limit non borné sur /api/lottery/leaderboard, événements de chat non validés Validation des entrées
Basse Validation d'un code promo non atomique Autorisations
Basse bcryptjs en JavaScript pur : coût processeur des tentatives de connexion Cryptographie et secrets

Pour reprendre un audit, dupliquer la grille, renseigner la colonne Preuve avec un lien vers le fichier ou le test, et ne marquer « Fait » qu'une mesure prouvée.