Files
_Assistant_Lead_Tech/10_backend_risques_et_vigilance.md
2026-03-16 15:36:53 +01:00

19 KiB
Raw Blame History

Back-end — Risques & vigilance

Ce fichier recense des risques back-end susceptibles de provoquer :

  • incidents prod,
  • failles de sécurité,
  • bugs non diagnostiquables,
  • régressions coûteuses,
  • incohérences de données.

Dernière mise à jour : 16-03-2026


Règles dutilisation

  • Chaque entrée doit dire :
    • ce qui peut mal se passer,
    • comment on le voit (symptômes),
    • comment on le maîtrise (mitigation).
  • Si cest lié à une stack / version : on note le contexte.

Index


AuthN/AuthZ dispersée (contrôles daccès au fil de leau)

Risques

  • Règles de permissions incohérentes selon endpoints
  • Failles “oubliées” sur un endpoint secondaire
  • Audit impossible

Symptômes

  • Utilisateurs qui accèdent à des ressources non prévues
  • Correctifs en urgence “on ajoute un if ici”
  • Bugs qui réapparaissent après refactor

Bonnes pratiques / mitigations

  • Centraliser authn/authz (middleware/policies)
  • Tests sur règles critiques
  • Logs/audit des décisions daccès

Guard global manquant (request.user jamais peuplé)

Risques

  • Chaîne auth bâtie sur une fondation inopérante (tout “a lair OK” en dev/tests, mais casse en prod)
  • Guards aval qui dépendent de request.user en erreur (ou contournements involontaires)
  • Découvert tard (souvent uniquement en code review ou en prod)

Symptômes

  • request.user vaut undefined dans un guard supposé “après auth”
  • Endpoints qui passent alors quils devraient être refusés (si les guards aval se désactivent/retournent true par défaut)
  • Tests “verts” car trop mockés (pas de test e2e qui valide le pipeline complet)

Bonnes pratiques / mitigations

  • Poser explicitement le guard global dès les foundations (au moins AuthGuard)
  • Vérifier lordre des APP_GUARD (AuthGuard avant tout guard qui lit request.user)
  • Ajouter au minimum 1 test dintégration/e2e qui prouve que request.user est bien peuplé sur un endpoint protégé

Duplication silencieuse de constantes partagées (contracts) via fichier orphelin

Risques

  • Deux sources de vérité qui divergent silencieusement (ex : topics officiels, enums métier, slugs)
  • Bug non détecté par TypeScript si la duplication est dans un fichier non importé (code mort)

Symptômes

  • Incohérences entre API et client sur des listes/enums “censées être partagées”
  • “Ça marche chez moi” selon lendroit où la constante est importée
  • Un fichier de config existe dans apps/* mais nest jamais importé/greffé au runtime

Bonnes pratiques / mitigations

  • Toute constante partagée vit dans packages/contracts/src/ et est importée depuis là (jamais recopiée dans apps/*)
  • En review : repérer les fichiers “config/constants” ajoutés dans apps/* sur des domaines déjà couverts par contracts
  • (Optionnel) Outillage : intégrer une étape de détection de code mort / exports inutilisés au CI si ça devient récurrent

Contrats API implicites (validation faible ou absente)

Risques

  • Entrées non validées → erreurs bizarres / vulnérabilités
  • Changements qui cassent le front et les intégrations

Symptômes

  • 500 sur erreurs utilisateur
  • Incohérences de format de réponse
  • “Ça marche en staging, pas en prod” (données réelles)

Bonnes pratiques / mitigations

  • Schémas (OpenAPI/JSON Schema) + validation serveur
  • Formats de réponse cohérents
  • Versionner/éviter breaking changes

Erreurs non standardisées (4xx/5xx incohérents)

Risques

  • Front et automatisations impossibles à rendre robustes
  • Debug long (pas de codes internes, pas de corrélation)

Symptômes

  • Clients qui “retry” sur des 4xx
  • Messages techniques exposés aux utilisateurs
  • Logs inexploitables

Bonnes pratiques / mitigations

  • Mapping HTTP standard + format derreur stable
  • Codes internes derreurs applicatives
  • requestId/traceId partout

Migrations risquées / non reproductibles

Risques

  • Downtime
  • Perte de données
  • Incohérence entre environnements

Symptômes

  • “Ça marche en local” mais pas en prod
  • Migration qui échoue à mi-chemin
  • Rollback impossible

Bonnes pratiques / mitigations

  • Migrations versionnées + tests staging
  • Stratégie expand/contract si besoin
  • Plan de rollback/mitigation

Non-idempotence sur opérations sensibles

Risques

  • Doubles paiements / doubles créations
  • Webhooks rejoués qui cassent létat

Symptômes

  • Doublons de lignes en DB
  • Actions exécutées 2 fois après timeout/retry
  • Incidents difficiles à reproduire

Bonnes pratiques / mitigations

  • Idempotency key sur endpoints critiques
  • Protection anti-doublon côté DB (contraintes uniques)
  • Comportement défini en cas de retry

Stripe (v17+) : confusion billing_cycle_anchor vs current_period_end

Risques

  • Stocker une date de fin de période incorrecte en DB (bug silencieux)
  • État dabonnement incohérent (UI, relances, accès premium)

Symptômes

  • currentPeriodEnd correspond à une date “bizarre” (souvent proche de la création), ou à un jour du mois
  • Des accès premium expirent trop tôt / trop tard

Bonnes pratiques / mitigations

  • Ne jamais interpréter billing_cycle_anchor comme une date de fin de période
  • Utiliser subscription.current_period_end (timestamp) pour la fin de période courante
  • Ajouter un test sur un événement webhook/Subscription qui vérifie la date persistée

PostgreSQL / Prisma : @unique sur champ nullable (idempotence cassée)

Risques

  • Doublons en base malgré un “unique” attendu (PostgreSQL autorise plusieurs NULL dans un index UNIQUE)
  • Upserts non idempotents si la clé peut être null (where: { externalId: null } crée plusieurs lignes)

Symptômes

  • Plusieurs enregistrements “équivalents” avec externalId = NULL
  • Rejouer un webhook / retry réseau crée une nouvelle ligne au lieu dupsert

Bonnes pratiques / mitigations

  • Toute clé utilisée dans un where dupsert doit être non-nullable
  • Si un identifiant externe peut légitimement être null, ne pas lutiliser comme clé didempotence : choisir une autre clé unique non-nullable

Observabilité insuffisante (logs non structurés, pas de corrélation)

Risques

  • MTTR très élevé : on devine
  • Incapacité à mesurer limpact utilisateur

Symptômes

  • Logs “ça a crash” sans contexte
  • Impossible de relier une requête à une erreur
  • Latence qui dérive sans alerte

Bonnes pratiques / mitigations

  • Logs structurés + requestId/traceId
  • Métriques de base (latence, erreurs, throughput)
  • Alertes simples sur 5xx/latence

Webhooks entrants — répondre 200 pendant processing (event perdu)

Risques

  • Le provider (Stripe, etc.) arrête ses retries après un 2xx, même si le premier worker a échoué
  • Event non appliqué mais marqué "traité" → état incohérent silencieux

Symptômes

  • Webhook reçu, 200 retourné, mais l'état en base n'est pas mis à jour
  • Aucun retry du provider → impossible à détecter sans monitoring actif

Bonnes pratiques / mitigations

  • Lock DB (WebhookEvent) avec machine d'état : pendingprocessingprocessed / failed
  • Si processing détecté (concurrent) : attendre brièvement la transition processed, sinon répondre non-2xx (force retry provider)
  • Ne jamais passer à processed sans preuve d'un traitement effectif
  • Contexte technique : Stripe / NestJS — 09-03-2026

Redis — thrash de connexion sous charge

Risques

  • Connexions concurrentes multiples si connect() est appelé "à la demande" sans lock
  • Spam logs + saturation connexions quand Redis est down ou lent

Symptômes

  • N appels simultanés → N tentatives de connexion en parallèle
  • Logs "Redis connection failed" en rafale au démarrage ou lors d'un restart Redis

Bonnes pratiques / mitigations

// Pattern single-flight + cooldown + fallback DB best-effort
if (!this.connectPromise) {
  this.connectPromise = this.client.connect().finally(() => { this.connectPromise = null; });
}
await this.connectPromise;
// Si échec → nextConnectRetryAtMs = now + 1000 → return false → fallback DB
  • Contexte technique : Redis / NestJS — 09-03-2026

Entitlements — TTL cache supérieur au SLA de propagation

Risques

  • TTL cache > SLA propagation → un webhook raté viole mécaniquement le SLA (accès stale plus long que garanti)
  • Utilisateur avec accès périmé ou sans accès dû, pendant toute la durée du TTL résiduel

Symptômes

  • Accès premium encore actif après annulation (ou inversement)
  • NFR "propagation ≤ 60s" non respecté en cas de webhook manqué

Bonnes pratiques / mitigations

  • TTL cache ≤ SLA cible (ex : NFR "≤ 60s" → TTL = 60s max)
  • Toujours coupler TTL + invalidation explicite via webhook (les deux, pas l'un ou l'autre)
  • Contexte technique : Redis / entitlements / NestJS — 09-03-2026

Guard NestJS route-level — null-check manquant sur request.user

Risques

  • Un guard route-level qui lit request.user.userId sans null-check lève une TypeError (500) si request.user est absent
  • Mauvaise registration de module, test d'intégration mal configuré, ou middleware custom peuvent produire cet état

Symptômes

  • TypeError: Cannot read properties of undefined (reading 'userId') en prod
  • Tests "verts" car request.user mocké globalement, mais pas le guard isolé

Bonnes pratiques / mitigations

const user = (request as any).user as { userId: string } | undefined;
if (!user?.userId) {
  throw new UnauthorizedException({ error: { code: 'UNAUTHENTICATED', message: '...' } });
}
  • Règle : les guards route-level ne font pas confiance aux guards globaux pour leurs invariants — ils se défendent eux-mêmes.
  • Contexte technique : NestJS v10+ — 09-03-2026

Compteurs in-memory ≠ métriques persistées

Risques

  • Compteurs in-memory remis à zéro au restart (perte de données)
  • Non agrégables sur plusieurs instances (données partielles par pod)

Symptômes

  • Métriques qui "repartent de 0" à chaque déploiement
  • Dashboards incorrects en environnement multi-instance

Bonnes pratiques / mitigations

  • V1 low-cost : Redis INCRBY best-effort par eventType → persisté et agrégé multi-instances
  • Évolutif vers Prometheus/OTel sans changer l'interface (abstraction dès le départ)
  • Contexte technique : Redis / NestJS — 09-03-2026

Interface provider incomplète ou divergente de ses implémentations

Risques

  • Une implémentation expose des méthodes non déclarées dans le contrat commun
  • Les appelants contournent linterface et se couplent à un provider concret
  • Une stratégie provider devient non interchangeable en pratique

Symptômes

  • Appels avec cast ou accès direct à une implémentation spécifique
  • Méthodes présentes dans une classe mais absentes de linterface
  • Régression lors dun changement de provider

Bonnes pratiques / mitigations

  • Toute capacité commune attendue par les appelants doit être déclarée dans linterface
  • Interdire les méthodes “cachées” consommées hors contrat
  • Tester au moins une implémentation par le contrat abstrait
  • Contexte technique : TypeScript / provider strategy — 10-03-2026

Boucle upsert N+1 sur synchronisation provider

Risques

  • Latence multipliée par le nombre ditems
  • Charge DB inutile
  • Timeouts ou contention sur gros volumes

Symptômes

  • Une boucle applicative exécute un upsert par item
  • Temps de traitement qui explose avec le volume
  • Logs SQL répétitifs et séquentiels

Bonnes pratiques / mitigations

  • Batcher quand cest possible
  • Précharger les données nécessaires avant boucle
  • Mesurer explicitement le coût dun upsert unitaire dans les flux de sync
  • Contexte technique : Prisma / synchronisation provider — 10-03-2026

Stripe list() sans gestion de has_more

Risques

  • Pagination tronquée silencieusement
  • Réconciliation incomplète dabonnements, achats ou moyens de paiement
  • Décisions métier prises sur un jeu de données partiel

Symptômes

  • Comportement correct sur petits comptes mais faux sur comptes plus chargés
  • Premiers éléments traités, les suivants ignorés
  • Absence de boucle de pagination ou dauto-pagination

Bonnes pratiques / mitigations

  • Traiter explicitement has_more
  • Utiliser lauto-pagination Stripe si adaptée
  • Tester au moins un cas avec plusieurs pages de résultats
  • Contexte technique : Stripe API — 10-03-2026

Concurrence entre activation locale et webhook sur transition trial → payant

Risques

  • Double création ou double attachement dune ressource unique
  • Conflit P2002
  • État local différent de létat Stripe pendant la transition

Symptômes

  • La transition fonctionne parfois, puis échoue aléatoirement
  • Un webhook Stripe et une action applicative écrivent la même mutation métier
  • Erreurs dunicité lors de lactivation payante

Bonnes pratiques / mitigations

  • Définir une seule source autorisée pour chaque transition détat
  • Rendre les écritures idempotentes
  • Sérialiser ou réconcilier explicitement les transitions pilotées à la fois par action utilisateur et webhook
  • Contexte technique : Stripe / Prisma / trial subscription — 10-03-2026

jest.clearAllMocks() dans des beforeEach imbriqués avec mocks Prisma

Risques

  • Remise à zéro dun setup attendu par un scope de test plus profond
  • Tests verts ou rouges pour de mauvaises raisons
  • Forte difficulté à comprendre létat réel des mocks

Symptômes

  • Comportement différent selon lordre ou le niveau dimbrication des describe
  • Mocks Prisma “perdus” entre deux tests
  • Corrections locales qui cassent dautres blocs de tests

Bonnes pratiques / mitigations

  • Centraliser la stratégie de reset des mocks
  • Éviter les clearAllMocks() concurrents à plusieurs niveaux de nesting
  • Préférer un setup explicite et local par scénario quand les mocks Prisma sont structurants
  • Contexte technique : Jest / Prisma / tests NestJS — 10-03-2026

Risques

  • Si la révocation DB échoue avant la suppression du cookie, lutilisateur garde un cookie local devenu incohérent
  • Lutilisateur peut rester bloqué dans un état où il ne peut plus se déconnecter proprement
  • Le comportement diffère selon la disponibilité de la base

Symptômes

  • Logout qui échoue par intermittence quand la DB est instable
  • Cookie de session toujours présent côté navigateur après erreur serveur
  • Réessais de logout qui produisent des états difficiles à diagnostiquer

Bonnes pratiques / mitigations

  • Toujours supprimer le cookie en premier, même si la révocation DB échoue ensuite
  • Traiter la suppression côté DB en best-effort ou avec gestion didempotence adaptée
  • Vérifier en test quun échec DB ne laisse pas laccès browser actif
  • Contexte technique : Next.js / auth par cookie / session persistée — 16-03-2026

Repository layer non branché (dead layer)

Risques

  • Donner une impression de sécurité alors que le code métier continue dappeler lORM directement
  • Multiplier les chemins daccès aux données avec des règles différentes
  • Payer le coût dune abstraction qui na aucun effet réel

Symptômes

  • Un repository est créé mais les anciens call sites Prisma restent en place
  • Les nouvelles règles de scoping ou de sécurité ne sappliquent pas partout
  • La review montre des fichiers de repository peu ou jamais importés

Bonnes pratiques / mitigations

  • Vérifier quune nouvelle couche dabstraction est réellement branchée dans les call sites existants
  • Rechercher explicitement les appels directs restants lors de la review
  • Refuser lintroduction dune couche repository tant que la migration effective nest pas faite
  • Contexte technique : TypeScript / Prisma / refactor daccès aux données — 16-03-2026