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 |
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.