Sprint 6 â Multi-environnements
Incidents du Sprint 6 : exposition et cloisonnement des environnements dev / staging / prod.
SEC-006 : dev et staging publiquement exposés en clair, branchés sur la base de production
SĂ©vĂ©ritĂ© : Critical Statut : â RĂ©solu â correctif #146 mergĂ© et validĂ© live le 2026-07-26 (MR !258)
Contexte : DĂ©couvert le 2026-07-26 pendant la session de validation live de #136, en rĂ©pondant Ă une question sur les certificats â donc totalement hors du pĂ©rimĂštre de la tĂąche en cours. Le multi-env structurel (#134) Ă©tait en place depuis le 2026-07-25, mais le socle d'isolation ne l'Ă©tait pas : #137 (databases sĂ©parĂ©es) et #138 (secrets / DNS / certificats par env) Ă©taient encore ouverts. Une consigne de pilotage disait « ne pas remonter le multi-env en live avant la fin du socle ».
Fichiers : k8s/overlays/{fastapi,frontend}/{dev,staging}/kustomization.yaml, k8s/base/externalsecret.yaml, k8s/platform/gateway.yaml
SymptĂŽme : curl http://dev.api.devopsyouss.com/healthz/ready â 200, en clair, depuis n'importe oĂč sur internet. Idem pour staging.api, dev.app et staging.app.
Cause : Trois briques qui font chacune correctement leur travail, et dont la composition produit un rĂ©sultat que personne n'a demandĂ©. (1) Le Gateway partagĂ© expose deux listeners, :80 et :443, tous deux en allowedRoutes: from: All â nĂ©cessaire, sinon le multi-env cross-namespace ne fonctionne pas. (2) Aucune HTTPRoute du dĂ©pĂŽt ne portait de sectionName : or une HTTPRoute sans sectionName s'attache Ă tous les listeners que le Gateway lui ouvre, pas seulement au bon. (3) ExternalDNS (source gateway-httproute) publie tout hostname rencontrĂ©, sans notion d'« environnement pas encore prĂȘt ». Aggravant : les overlays dev et staging ne surchargent pas l'ExternalSecret â le base tire la clĂ© fastapi-eks/app depuis AWS Secrets Manager, la mĂȘme pour les trois envs, donc dev et staging tapent la base de production. Et le tag de dev est bumpĂ© automatiquement par le write-back Ă chaque merge sur develop : du code n'ayant passĂ© ni staging ni E2E servait publiquement sur les donnĂ©es de prod. Le risque d'intĂ©gritĂ© prĂ©cĂ©dait le risque d'intrusion.
SymptĂŽme trompeur qui a failli faire classer l'affaire : la premiĂšre alerte n'Ă©tait pas le 200 en clair, c'Ă©tait une erreur de certificat sur https://dev.api.devopsyouss.com (le wildcard *.devopsyouss.com ne couvre qu'un seul niveau de label, donc pas dev.api.). On pouvait en conclure « dev est protĂ©gĂ© ». Faux : un certificat invalide n'est pas un contrĂŽle d'accĂšs, c'est le client qui dĂ©cide de l'accepter â et il suffisait d'enlever le s de https pour tout contourner sans le moindre avertissement.
Fix (#146, MR !258) : les 4 overlays de dev et staging suppriment la HTTPRoute hĂ©ritĂ©e du base ($patch: delete) au lieu de lui donner un hostname. Sans route, rien ne s'attache au Gateway et ExternalDNS ne publie aucun enregistrement. Les envs restent joignables par leur Service ClusterIP et par kubectl port-forward. Alternative Ă©cartĂ©e : garder les routes et restreindre l'accĂšs (allowlist IP, basic auth) â le problĂšme n'est pas « qui accĂšde Ă dev » mais « dev Ă©crit dans la base de prod », donc une allowlist aurait rĂ©duit le nombre de personnes capables de corrompre les donnĂ©es sans supprimer le chemin, tout en ajoutant une configuration Ă maintenir puis Ă retirer.
Validation : MergĂ© puis synchronisĂ© par ArgoCD le 2026-07-26. kubectl get httproute -A ne renvoie plus que 4 routes, toutes en prod ou plateforme (fastapi-prod, frontend-prod, monitoring, argocd) â aucune dans les 4 namespaces dev/staging. Preuve la plus forte cĂŽtĂ© ExternalDNS, qui dĂ©rive ses enregistrements des HTTPRoutes : action=DELETE du CNAME et des deux TXT de propriĂ©tĂ© (<host> et cname-<host>, marque txtOwnerId=fastapi-eks) pour les 4 hosts, en deux passes de rĂ©conciliation. Non-rĂ©gression prod : curl https://api.devopsyouss.com/healthz/ready â {"status":"ok"}. AccĂšs lĂ©gitime prĂ©servĂ© : port-forward sur fastapi-dev â {"status":"ok"}. ConfirmĂ© une seconde fois le 2026-07-28 sur un cluster montĂ© de zĂ©ro â cas qui n'avait jamais Ă©tĂ© exercĂ©, la premiĂšre validation portant sur un cluster dĂ©jĂ en place : aprĂšs bootstrap complet et synchronisation ArgoCD, kubectl get httproute -A ne montre aucune route dans les 4 namespaces dev/staging. L'exposition ne se reproduit donc pas au dĂ©marrage, qui Ă©tait prĂ©cisĂ©ment le chemin par lequel elle s'Ă©tait produite.
Leçon : Un garde-fou documentaire n'est pas un contrĂŽle. La consigne « ne pas remonter le multi-env en live avant la fin du socle » existait, et n'a servi Ă rien : elle n'Ă©tait Ă©crite nulle part oĂč elle s'appliquait. L'exposition s'est faite toute seule au dĂ©marrage du cluster, sans qu'aucun humain ait Ă agir â ArgoCD synchronise, les routes s'attachent, ExternalDNS publie. C'est la deuxiĂšme occurrence du mĂȘme schĂ©ma sur ce projet, aprĂšs le gate prod « documentaire » (CODEOWNERS en place mais l'approbation obligatoire est payante, donc il suggĂšre sans bloquer). Deux instances indĂ©pendantes, ce n'est plus une anecdote : une rĂšgle qui dĂ©pend de la mĂ©moire d'un humain au bon moment est un vĆu, pas un contrĂŽle. Quand on Ă©crit « il ne faut pas faire X », il faut aussitĂŽt se demander qu'est-ce qui empĂȘche X ? â sans rĂ©ponse, la note ne protĂšge rien. Correctif de fond encore Ă venir : #137 et #138. Voir le guide Exposition involontaire et l'ADR 029.
SEC-007 : aucune redirection HTTP vers HTTPS, identifiants transmis en clair (production incluse)
SĂ©vĂ©ritĂ© : High Statut : â RĂ©solu â correctif #147 mergĂ© (MR !257) et validĂ© live le 2026-07-28 sur un cluster montĂ© de zĂ©ro
Contexte : Découvert le 2026-07-26 en instruisant SEC-006. Le défaut est antérieur au multi-env : il touche la production, publique depuis le Sprint 4. Le multi-env n'a fait que multiplier la surface, de 2 endpoints à 6.
Fichiers : k8s/base/httproute.yaml, k8s/frontend/httproute.yaml, k8s/platform/argocd/httproute-argocd.yaml, k8s/platform/monitoring/httproute-grafana.yaml, k8s/platform/gateway.yaml
SymptĂŽme : curl http://api.devopsyouss.com/healthz/ready â 200. Les 4 services publics (api, app, grafana, argocd) rĂ©pondent en clair aussi bien qu'en TLS.
Cause : MĂȘme mĂ©canisme que SEC-006 â aucune HTTPRoute ne porte de sectionName, donc toutes s'attachent aux deux listeners du Gateway. Aucun RequestRedirect, aucun ClientTrafficPolicy nulle part dans le dĂ©pĂŽt. Le TLS existait et fonctionnait ; rien n'obligeait le client Ă l'emprunter. C'est une faille et pas un dĂ©tail de configuration : POST /login accepte identifiants et mot de passe et renvoie un JWT (app/routers/auth.py), et la CLI argocd transporte un token de session. Sur http://, les deux sont lisibles par tout observateur du rĂ©seau. CWE-319, Cleartext Transmission of Sensitive Information.
Fix (#147, MR !257) : deux piĂšces qui ne jouent pas le mĂȘme rĂŽle. (1) sectionName: https sur les 4 routes applicatives â c'est ceci qui ferme le clair, en les retirant du listener :80. (2) Une HTTPRoute de redirection (k8s/platform/httproute-https-redirect.yaml) sur sectionName: http, sans hostnames donc attrape-tout, avec un filtre RequestRedirect en 301 â elle ne ferme rien, elle offre une sortie propre plutĂŽt qu'un 404 sec, et le 301 est mis en cache par le navigateur. La redirection n'a ni hostname ni backend (un RequestRedirect rĂ©pond au niveau du Gateway), elle vit donc dans le namespace plateforme fastapi sans y avoir de Service. PosĂ©e par le bootstrap Ansible et non en GitOps (ADR 010) : elle dĂ©crit le comportement d'un listener, elle appartient Ă la plomberie du Gateway.
Validation : Hors cluster d'abord â kube-linter sans erreur, kubectl kustomize sur les 6 overlays (le sectionName du base coexiste avec le patch parentRefs/0/namespace des overlays), mkdocs build. ValidĂ©e en live le 2026-07-28, sur un cluster montĂ© de zĂ©ro aprĂšs merge, ce qui Ă©tait le point non trivial : la redirection n'Ă©tant pas en GitOps, le merge seul ne prouvait rien sur son dĂ©ploiement.
Les 4 services publics renvoient 301 -> https://<host>/ (api, app, grafana, argocd), lĂ oĂč ils rĂ©pondaient tous 200 en clair le 2026-07-26. Non-rĂ©gression du TLS : curl https://api.devopsyouss.com/healthz/ready â {"status":"ok"}. Cause racine fermĂ©e et pas seulement son symptĂŽme â kubectl get httproute -A ne montre plus aucune route sans sectionName :
argocd argocd https
fastapi-prod fastapi https
fastapi https-redirect http
frontend-prod frontend https
monitoring grafana https
Preuve du chemin de dĂ©ploiement : kubectl get httproute https-redirect -n fastapi renvoie bien la route, sans hostname, posĂ©e par le bootstrap Ansible au montage, sans intervention manuelle. â ïž PiĂšge de dĂ©ploiement, toujours valable : sur un cluster dĂ©jĂ montĂ©, un merge ne suffit pas â il faut rejouer le bootstrap ou appliquer le fichier Ă la main.
Leçon : Le chiffrement disponible n'est pas le chiffrement imposĂ©. Un listener ouvert que personne n'a dĂ©cidĂ© d'utiliser reste un chemin exploitable, et il suffit d'un caractĂšre en moins dans une URL pour l'emprunter. Corollaire d'architecture propre Ă Gateway API : un parentRef sans sectionName est un attachement implicite Ă tout â c'est un dĂ©faut par omission, invisible Ă la relecture d'un manifeste pris isolĂ©ment, et il faut le chercher activement. Point de vigilance associĂ© : le :80 ne doit dĂ©sormais plus servir Ă rien d'autre ; les certificats passent par DNS-01, donc aucun challenge HTTP-01 Ă prĂ©server, mais un futur ClusterIssuer HTTP-01 serait aveuglĂ© par la route attrape-tout. Voir le guide Exposition involontaire et l'ADR 017 DĂ©cision 4.
INC-062 : la rétention ECR a supprimé l'image servie par staging et prod
SĂ©vĂ©ritĂ© : High Statut : â RĂ©solu â rĂ©tention Ă©largie et appliquĂ©e, staging et prod repointĂ©s, validĂ© live le 2026-07-28 (#150)
Contexte : Découvert le 2026-07-28 en cherchant pourquoi le schedule registry-scan (Phase 1 vuln-mgmt, #118) échouait tous les matins depuis le 2026-07-27. Le job était rouge depuis trois exécutions consécutives sans que personne ne l'ait relié au passage en multi-env du 2026-07-25. Le cluster étant éteint, l'impact réel restait invisible.
Fichiers : terraform/modules/ecr/main.tf, terraform/persistent/variables.tf, k8s/overlays/{fastapi,frontend}/{staging,prod}/kustomization.yaml
SymptĂŽme : Le job registry-scan Ă©choue avec MANIFEST_UNKNOWN: Requested image not found sur deployed-de3dcf4e..., le tag rĂ©fĂ©rencĂ© par l'overlay de prod. L'inventaire ECR confirme qu'il ne reste que 5 tags deployed-, tous poussĂ©s dans la mĂȘme soirĂ©e du 2026-07-26 entre 17h15 et 20h41. L'image de prod n'existe plus, ni dans fastapi-eks/fastapi, ni dans fastapi-eks/frontend.
Cause : La lifecycle policy ECR conserve les N derniĂšres images portant le prĂ©fixe deployed- (deployed_retention_count, alors Ă 5). Le dĂ©coupage deployed- / candidate- mis en place en #104 protĂšge l'image servie contre la rĂšgle d'Ăąge des candidates, mais pas contre le count : les trois environnements rĂ©fĂ©rencent des tags du mĂȘme moule et concourent donc dans la mĂȘme fenĂȘtre de rĂ©tention. Or leurs cadences sont opposĂ©es. Dev est bumpĂ© automatiquement Ă chaque merge sur develop (write-back reciblĂ© en #139), tandis que staging et prod restent figĂ©s entre deux promotions dĂ©libĂ©rĂ©es (scripts/promote.sh, ADR 029 D2). Le 2026-07-26 au soir, cinq merges en trois heures et demie ont suffi Ă faire sortir l'image de prod de la fenĂȘtre. Aggravant : comme la rĂšgle 1 rĂ©clamait cette image, son expiration a emportĂ© l'image entiĂšre, donc aussi son tag candidate- jumeau â il n'y a rien Ă retaguer, le digest a disparu.
Fix (#150) : deployed_retention_count portĂ© de 5 Ă 30, puis terraform apply sur le stack persistent (appliquĂ© en local, la CI infra ne gĂšre que terraform/ephemeral). Il faut corriger la rĂ©tention avant de promouvoir, sinon les deux merges de promotion consomment eux-mĂȘmes deux places et l'image fraĂźchement promue se retrouve Ă trois merges de l'Ă©jection. Staging et prod sont ensuite repointĂ©s sur deployed-e9f2fd64..., seule cible saine encore prĂ©sente, par deux MR de promotion successives (dev vers staging, puis staging vers prod). Solution Ă©cartĂ©e pour l'instant : marquer l'image Ă la promotion avec un prĂ©fixe dĂ©diĂ© (inuse-<env>-<sha>) protĂ©gĂ© par une rĂšgle en prioritĂ© 1. C'est le seul fix qui ferme rĂ©ellement le trou, mais il impose un nouveau job CI qui pose un tag ECR au merge de la MR de promotion, promote.sh ne parlant qu'Ă Kustomize. Un tag mobile type inuse-prod est impossible, les deux repos sont en IMMUTABLE.
Validation : Faite en live le 2026-07-28 au montage du cluster. Les six workloads démarrent, aucun ImagePullBackOff, et kubectl get events -A --field-selector reason=Failed | grep -i pull ne renvoie rien. Les images réellement servies correspondent aux overlays :
fastapi-prod âŠ/fastapi:deployed-340a8e92âŠ
fastapi-staging âŠ/fastapi:deployed-340a8e92âŠ
frontend-prod âŠ/frontend:deployed-340a8e92âŠ
frontend-staging âŠ/frontend:deployed-340a8e92âŠ
fastapi-dev âŠ/fastapi:deployed-905509fbâŠ
frontend-dev âŠ/frontend:deployed-905509fbâŠ
RĂ©tention vĂ©rifiĂ©e cĂŽtĂ© AWS aprĂšs apply : countNumber = 30 sur la rĂšgle 1 des deux dĂ©pĂŽts. Le compteur de tags deployed- est passĂ© de 5 Ă 7 pendant la session â sous l'ancienne valeur, les merges de la journĂ©e auraient supprimĂ© deux images de plus. Reste Ă confirmer : registry-scan ne pourra repasser au vert qu'au prochain schedule du matin avec le ci_config_path rebasculĂ© sur l'application. Le tag qu'il cherche, deployed-340a8e92, est prĂ©sent dans les deux dĂ©pĂŽts.
Leçon : Un mĂ©canisme de protection reste valide tant que ses hypothĂšses le sont. Le dĂ©coupage de #104 Ă©tait correct en mono-environnement, oĂč l'image servie Ă©tait toujours la derniĂšre produite, donc structurellement en tĂȘte de fenĂȘtre et jamais menacĂ©e par un count. Le passage en multi-env a supprimĂ© cette propriĂ©tĂ© sans que personne ne relise la rĂšgle, parce que le changement portait sur Kustomize et ArgoCD, pas sur Terraform. La revue d'impact d'un changement d'architecture doit descendre jusqu'aux garde-fous Ă©crits pour l'architecture prĂ©cĂ©dente, y compris ceux qui vivent dans une autre couche de la stack. Corollaire opĂ©rationnel : une politique de rĂ©tention de registre ne sait pas ce qui tourne dans le cluster, elle ne connaĂźt que des dates et des compteurs. Tant qu'aucun tag ne distingue l'image en service, Ă©largir la fenĂȘtre rend l'incident improbable sans le rendre impossible. Et le seul signal qui a levĂ© le liĂšvre Ă©tait un job de scan rouge, symptĂŽme indirect : sans lui, le problĂšme serait apparu au prochain dĂ©marrage du cluster, sous la forme d'une prod qui ne dĂ©marre pas.
INC-063 : plus aucune métrique de l'API collectée depuis le cutover multi-env
SĂ©vĂ©ritĂ© : High Statut : â RĂ©solu â correctif #152 mergĂ© (MR !268) et validĂ© live le 2026-07-28
Contexte : DĂ©couvert le 2026-07-28 en validant #135, par un contrĂŽle de non-rĂ©gression. Le correctif NetworkPolicy restreignait l'ingress de l'API Ă Envoy et Prometheus ; il fallait donc vĂ©rifier que Prometheus scrapait toujours. La requĂȘte a renvoyĂ© zĂ©ro cible, ce qui a d'abord ressemblĂ© Ă une rĂ©gression du correctif.
Fichiers : k8s/platform/monitoring/servicemonitor-fastapi.yaml, k8s/platform/monitoring/prometheusrules-fastapi.yaml
SymptÎme : /api/v1/targets ne renvoie aucune cible dont le namespace commence par fastapi. En parallÚle, l'alerte FastAPITargetMissing est en firing / critical depuis le démarrage du cluster.
Cause : Le ServiceMonitor portait namespaceSelector.matchNames: [fastapi]. Or le cutover multi-env (#134, 2026-07-25) a fait de fastapi un namespace plateforme qui n'hĂ©berge plus que le Gateway : il ne contient aucun Service. Les Services applicatifs vivent dĂ©sormais dans fastapi-dev, fastapi-staging et fastapi-prod, tous correctement labellisĂ©s app: fastapi. Le sĂ©lecteur pointait donc sur un namespace vide. Aggravant : les rĂšgles PrometheusRule sont Ă©pinglĂ©es sur le mĂȘme namespace="fastapi", donc FastAPITargetDown et FastAPIPodCrashLooping Ă©taient elles aussi inertes. Et les deux alertes RED n'avaient aucun filtre de namespace : rĂ©parer le seul scrape aurait fait paginer un pic d'erreurs de dev comme s'il venait de la prod.
Ăcart de diagnostic Ă retenir : une NetworkPolicy qui bloque un scrape laisse la cible visible en down, avec un lastError. ZĂ©ro cible signifie que la dĂ©couverte de service n'a rien trouvĂ©, ce qui se situe en amont du rĂ©seau. C'est ce raisonnement qui a disculpĂ© #135 en une minute au lieu d'un revert inutile.
Fix : namespaceSelector: {any: true} sur le ServiceMonitor, et non une nouvelle liste de namespaces â reconduire une liste figĂ©e, c'est reprogrammer la mĂȘme panne au prochain changement de topologie. Le couple any: true + selector: app=fastapi suit les Services oĂč qu'ils aillent, et le label namespace reste portĂ© par chaque sĂ©rie. Les 5 rĂšgles d'alerte sont recentrĂ©es sur fastapi-prod : on page sur ce qui sert les utilisateurs. L'alerting par environnement relĂšve de #141.
Validation : Faite en live le 2026-07-28, juste aprĂšs le merge. Les 5 cibles apparaissent, une par pod, sur les trois environnements, toutes en up :
fastapi-dev | up
fastapi-prod | up
fastapi-prod | up
fastapi-staging | up
fastapi-staging | up
FastAPITargetMissing a cessĂ© de sonner ; il ne reste que Watchdog et les deux alertes d'overcommit, attendues. Ătape intermĂ©diaire instructive : Ă la dĂ©couverte, les cibles sont apparues en unknown â elles existent mais n'ont pas encore Ă©tĂ© scrapĂ©es. C'est un troisiĂšme Ă©tat, distinct de up et de down, qu'il faut savoir attendre (intervalle 30s) avant de conclure Ă un Ă©chec. RĂ©fĂ©rences rĂ©siduelles Ă l'ancienne topologie nettoyĂ©es dans docs/validation-runbook.md et docs/comprendre/alerting.md.
Leçon : Un garde-fou peut fonctionner parfaitement et ne servir Ă rien. FastAPITargetMissing a Ă©tĂ© Ă©crite exactement pour ce cas â son commentaire dit « absent() renvoie 1 quand la sĂ©rie est introuvable => couvre ce trou » â et elle a correctement sonnĂ© en critical pendant les trois jours d'uptime qui ont suivi le cutover. Ce qui a manquĂ©, c'est le regard sur l'alerte, pas l'alerte. Une chaĂźne de dĂ©tection ne vaut que par son dernier maillon, qui est humain. Corollaire de conception : un sĂ©lecteur qui nomme explicitement une ressource (matchNames, un namespace en dur dans une expr) crĂ©e une dĂ©pendance silencieuse Ă la topologie â il ne casse pas quand elle change, il cesse de faire son travail. C'est la troisiĂšme occurrence du motif sur ce sprint aprĂšs SEC-006 et INC-062 : un changement d'architecture doit ĂȘtre suivi jusque dans les objets Ă©crits pour l'architecture prĂ©cĂ©dente, mĂȘme quand ils vivent dans une autre couche.
INC-064 : trivy-image-scan rouge sur deux CVE d'outillage jamais installé
SĂ©vĂ©ritĂ© : High Statut : â RĂ©solu â correctif #155
Contexte : DĂ©couvert le 2026-07-30 en ouvrant !271 (#137), dont le pipeline est tombĂ© rouge alors que la MR ne touche ni Dockerfile, ni requirements.txt, ni aucun fichier Python. Le mĂȘme code applicatif passait au vert le 2026-07-28 Ă 18:45 (pipeline 2713010874, sha 332fcf1c), et l'image de base est pinnĂ©e par digest : elle n'avait pas bougĂ©. !271 a Ă©tĂ© mergĂ©e malgrĂ© ce rouge, l'Ă©chec Ă©tant prouvĂ© indĂ©pendant, ce qui a laissĂ© develop rouge sur ce job.
Fichiers : Dockerfile, .dockerignore
SymptÎme : trivy-image-scan échoue sur l'image backend avec deux vulnérabilités de sévérité haute : msgpack 1.1.2 (GHSA-6v7p-g79w-8964, corrigé en 1.2.1) et setuptools 70.3.0 (CVE-2025-47273, corrigé en 78.1.1). Ni l'un ni l'autre n'apparaßt dans requirements.txt, et la trace du job ne donne pas leur PkgPath.
Cause : Aucun des deux paquets n'est installĂ© dans l'image. Trivy les lit dans pip/_vendor/vendor.txt, le manifeste des dĂ©pendances vendorisĂ©es de pip, qu'il interprĂšte comme un inventaire de paquets installĂ©s â d'oĂč le PkgPath vide. L'image portait deux pip : celui de l'image de base (25.0.1, qui dĂ©clare msgpack==1.1.0) et celui du venv, montĂ© en 26.2 par la ligne pip install --no-cache-dir --upgrade pip wheel du stage builder puis recopiĂ© tel quel par COPY --from=builder /venv /venv (qui dĂ©clare msgpack==1.1.2). La mise Ă jour de la base de vulnĂ©rabilitĂ©s de Trivy a fait franchir le seuil Ă ces deux lignes : la CVE est arrivĂ©e au monde, pas au projet. Le commentaire du Dockerfile affirmait dĂ©jà « ni pip, ni outils de build » â il dĂ©crivait une intention, pas l'image rĂ©ellement produite.
Ăcart de diagnostic Ă retenir : le bump du digest de base, premiĂšre voie envisagĂ©e dans l'issue, n'aurait rien corrigĂ©. Le pip fautif est installĂ© par le Dockerfile lui-mĂȘme, pas hĂ©ritĂ© de la base, et le pip de la base vendorise une version de msgpack Ă©galement sous le seuil. Un PkgPath vide dans un rapport Trivy n'est pas un dĂ©tail cosmĂ©tique : il signale que le paquet n'a pas de dist-info, donc qu'il n'a jamais Ă©tĂ© installĂ©.
Fix : Suppression de pip et wheel du stage runtime (venv et site-packages de la base), plutĂŽt qu'une exception VEX. Rien n'installe de paquet Ă l'exĂ©cution, la suppression est donc sans effet fonctionnel et retire la cause au lieu d'annoter le symptĂŽme â une exception VEX aurait dĂ» ĂȘtre réécrite Ă chaque future CVE d'une dĂ©pendance vendorisĂ©e de pip. CorrigĂ© dans la foulĂ©e : frontend/ n'Ă©tait pas dans .dockerignore, donc la source du frontend partait dans l'image backend, et tout node_modules prĂ©sent dans le workspace de build y aurait injectĂ© ses propres CVE.
Validation : Faite en local sur l'image buildĂ©e depuis le Dockerfile corrigĂ©, avec les flags exacts du job CI (gate --exit-code 1 sur les sĂ©vĂ©ritĂ©s hautes et au-dessus, --ignore-unfixed, --ignorefile .trivyignore, --vex) : le gate sort en 0, contre deux vulnĂ©rabilitĂ©s hautes avant. Non-rĂ©gression applicative vĂ©rifiĂ©e sur la mĂȘme image : import app.main passe, uvicorn dĂ©marre, /healthz/live et /docs rĂ©pondent 200. Effet de bord mesurĂ© : 269 Mo â 234 Mo, l'essentiel venant de l'exclusion du frontend. Confirmation attendue par le trivy-image-scan de develop aprĂšs merge.
Leçon : Un scanner rapporte ce qu'il sait lire, pas ce qui est installĂ©. Trivy dĂ©duit un inventaire de fichiers manifestes, et vendor.txt en est un sans qu'aucun paquet ne soit dĂ©ployable derriĂšre. Prendre le rapport pour l'Ă©tat rĂ©el de l'image conduit Ă traiter une remontĂ©e lĂ oĂč elle est affichĂ©e plutĂŽt que lĂ oĂč elle naĂźt â ici, Ă bumper une image de base qui n'y Ă©tait pour rien. Le rĂ©flexe utile est de localiser physiquement le paquet dans l'image avant de choisir le traitement : c'est ce qui a fait apparaĂźtre qu'il n'y avait rien Ă corriger, seulement de l'outillage Ă ne pas embarquer. Second enseignement, sur le rouge lui-mĂȘme : un job de scan peut virer sans qu'une seule ligne du dĂ©pĂŽt ait changĂ©, puisque son verdict dĂ©pend d'une base de donnĂ©es externe et mouvante. Un pipeline rouge n'est donc pas toujours une rĂ©gression, et l'Ă©cart entre le dernier vert connu et le premier rouge est la premiĂšre mesure Ă faire.