Skip to content

fix(security): suite de la remédiation de l'audit de sécurité V1 - #4720

Merged
LucasCharrier merged 10 commits into
masterfrom
fix/secu-audit-v1-remaining
Oct 5, 2026
Merged

LucasCharrier merged 10 commits into
masterfrom
fix/secu-audit-v1-remaining

Conversation

@LucasCharrier

@LucasCharrier LucasCharrier commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Contexte

Suite de la remédiation de l'audit de sécurité de la V1 (#4681).

Changements

API : réglages booléens lus strictement
EGAPRO_SEND_EMAILS, EGAPRO_SMTP_SSL et EGAPRO_READONLY étaient convertis avec bool(), donc "false" valait vrai. Ils acceptent maintenant 1/true/yes/on et 0/false/no/off/vide, et toute autre valeur bloque le démarrage. Les valeurs déployées en prod, en preprod et sur les review apps ont été vérifiées : elles gardent leur sens actuel. Le vocabulaire du parseur et la liste des flavours autorisées à garder les secrets de dev ne peuvent plus être redéfinis par une variable EGAPRO_*.

nginx : connexions upgradées et en-têtes CSP entrants
L'app n'utilise pas WebSocket : les requêtes Upgrade sont refusées (426) sur toutes les locations, avant d'atteindre Next.js. Les en-têtes Content-Security-Policy, Content-Security-Policy-Report-Only et X-Nonce envoyés par le client ne sont jamais transmis à l'app.

Sentry 8 → 11
@sentry/nextjs passe en v11, ce qui corrige @opentelemetry/core. dataCollection coupe la collecte des cookies, en-têtes, corps de requête, query strings et données utilisateur, et les en-têtes joints aux erreurs du middleware passent en liste blanche. Les configurations serveur et edge sont chargées depuis instrumentation.ts, le SDK ne les injectant plus. Le mode debug est coupé, retiré des bundles et du plugin de build.

CSP à nonce pour les scripts
script-src n'autorise plus 'unsafe-inline' ni aucun hôte : le middleware génère un nonce par requête, écrase tout en-tête CSP ou nonce venant du client, et 'strict-dynamic' ne fait confiance qu'aux scripts noncés et à ceux qu'ils chargent. Next.js, react-dsfr et Matomo reprennent le nonce, qui n'est jamais renvoyé au navigateur. base-uri est limité à 'self'. La page d'accueil n'est plus forcée en statique, une page pré-rendue ne pouvant pas porter de nonce. style-src garde 'unsafe-inline', que le DSFR exige pour ses attributs style.

E2E
test-login rattache le compte de test à son SIREN, comme une connexion ProConnect. Sans ce rattachement, le nettoyage entre deux tests ne supprimait aucune déclaration, et la première déclaration créée faisait échouer tous les tests suivants.

Vérification

  • App : tsc, lint, 509 tests Jest, next build.
  • API : 439 tests pytest.
  • Build de production démarré en local : tous les scripts portent le nonce, aucune violation CSP dans la console, navigation côté client fonctionnelle, en-tête CSP falsifié ignoré.
  • nginx : configuration validée (nginx -t) et testée en conteneur, 426 et en-têtes vidés sur chaque location.

« false », « 0 » ou une valeur vide valent non, une valeur ambiguë bloque le démarrage. Les valeurs déployées (envoi des emails, TLS SMTP, mode lecture seule) gardent leur sens actuel.
… entrants

L'app n'utilise pas WebSocket : les requêtes Upgrade sont refusées avant Next.js. Les en-têtes Content-Security-Policy et X-Nonce venant du client ne sont jamais transmis à l'app.
Montée 8 → 11 (corrige @opentelemetry/core). Cookies, en-têtes, corps de requête, query strings et données utilisateur ne sont pas collectés. La configuration serveur et edge est chargée depuis instrumentation.ts, mode debug coupé.
script-src n'autorise plus 'unsafe-inline' : chaque réponse porte un nonce aléatoire, et les en-têtes CSP ou nonce envoyés par le client sont écrasés. La page d'accueil est rendue par requête pour recevoir le nonce.
Une PR hebdomadaire groupe les montées mineures et correctives des dépendances directes, les majeures restent manuelles.
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 08:00 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 08:00 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 08:00 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 08:00 — with GitHub Actions Active
@socket-security

socket-security Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​sentry/​nextjs@​8.51.0 ⏵ 11.1.097 +210082 -896 +1100

View full report

@iterion-forge-egapro iterion-forge-egapro Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revi — 1 problème à corriger.

1 medium

À confirmer

  • Was client-side Sentry verified after the v11 bump (a production browser session showing the client SDK still initializes)? The build plugin's injection of sentry.client.config.ts is the legacy mechanism on Next 14 (instrumentation-client.ts needs Next 15.3+), and since debug is now off a silent injection failure would mean client error monitoring disappears with no visible symptom.
Détails du run IA · Iterion

Run : 01a10b14-1975-7878-8b4d-82d00c9de0aa

Tokens du run au moment de publier : 118 595.

Périmètre revu : correctness: packages/api/egapro/config.py (parse_bool + init bool branch), packages/api/test/test_config.py ; correctness: packages/app/src/middleware.ts (nonce generation, request/response header propagation, CSP construction) ; correctness: packages/app/src/app/(default)/page.tsx force-dynamic vs root layout headers() usage (no other force-static route remains) ; security: packages/nginx/nginx.conf (upgrade guard, CSP/X-Nonce header stripping, coverage of all proxy locations) ; security: packages/app/src/common/sentryDataCollection.ts + sentry.{client,edge,server}.config.ts (PII collection off, tunnel kept) ; security/maintainability: packages/app/src/instrumentation.ts (per-runtime config import paths), packages/app/next.config.js (withSentryConfig v11 shape, hidden source maps + delete-after-upload) ; dependencies: pnpm-lock.yaml diff (Sentry 8→11 tree swap only, @opentelemetry/api 1.9.0→1.9.1), packages/app/package.json ; tests: packages/app/src/tests/middleware.test.ts (nonce uniqueness, forged CSP/nonce overwrite, dev-only unsafe-eval) ; maintainability: .github/dependabot.yml (target-branch master matches this PR's base branch)

Revue mono (single model family).

Identifiants des findings : Rcb2640.

Étape Modèle Harness Effort demandé Tokens
Revue GLM glm-5.3 claude_code max 82 918
Synthèse · GLM glm-5.3 claude_code medium 35 677

Tokens cumulés des appels IA, pas la taille du contexte. L’effort indiqué est le réglage demandé au modèle.

Comment thread packages/nginx/nginx.conf Outdated
@maxgfr maxgfr linked an issue Oct 5, 2026 that may be closed by this pull request

@maxgfr maxgfr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rien de bloquant côté CSP, Sentry, nginx ou API. Vérifié en lecture seule sur la review app, sur 12 pages (accueil, recherche, simulateur, déclaration, login, /stats, une 404) :

  • tous les <script> portent le nonce, et il change à chaque requête ;
  • un en-tête Content-Security-Policy ou X-Nonce envoyé par le client n'est pas repris ;
  • une requête Upgrade: websocket reçoit un 426, /healthz répond 200 ;
  • aucun .map n'est servi.

Important

Dependabot npm reste inactif (.github/dependabot.yml:11-13). La branche par défaut du dépôt est alpha, et Dependabot ne lit sa configuration que sur la branche par défaut : ce fichier sur master est ignoré. Il faut plutôt ajouter, dans le dependabot.yml d'alpha, une entrée npm avec target-branch: master (deux entrées pour le même écosystème et le même dossier sont acceptées si leur target-branch diffère), sans appliquer le groupe "*" à l'entrée qui cible alpha (cohérence de version ultra11y).

https://*.gouv.fr dans script-src limite l'apport du nonce (middleware.ts:29 et :35). Non exploité, mais plausible : une injection HTML peut charger un script depuis n'importe quel sous-domaine gouv.fr qui sert du JS contrôlable, ou poser un <base href> vers un tel hôte (autorisé par base-uri), et les chunks Next noncés seraient alors chargés depuis là. Suggestion : 'strict-dynamic' (tous les scripts rendus côté serveur sont déjà noncés, et Matomo comme les chunks sont injectés par du JS de confiance) ou la seule origine Matomo à la place de *.gouv.fr, et base-uri 'self'.

À confirmer : EGAPRO_SMTP_SSL en prod (config.py:83-84). Si la valeur stockée était "False" ou "0", l'ancien bool() activait STARTTLS (emails/__init__.py:58) et le nouveau parseur le coupe : le login SMTP partirait en clair. Dev, preprod et docker-compose gardent bien leur sens, mais je n'ai pas pu vérifier la prod.

Check « Test e2e » : les deux échecs précédents viennent du délai de l'étape « Wait for app to be ready » (180 s écoulées avant la fin du déploiement), pas d'un test. Le run actuel est encore en cours.

Mineurs

  • config.py:58-59 : TRUE_VALUES / FALSE_VALUES sont en majuscules, donc init() les traite comme des réglages, et une variable EGAPRO_TRUE_VALUES peut les écraser. Le préfixe _ ne suffit pas ("_X".isupper() vaut True) : les passer en minuscules ou les définir dans la fonction.
  • test_config.py:104-113 : config.init() modifie l'état global, et SMTP_SSL reste à True pour les tests suivants. Restaurer avec monkeypatch.setattr.
  • nginx.conf:113-120 :
    • le 426 et la suppression d'en-têtes ne s'appliquent qu'à location / (pas exploitable, nginx ne transmet pas Upgrade par défaut), et proxy_set_header Upgrade "" est redondant ;
    • Next lit aussi Content-Security-Policy-Report-Only en repli : autant le vider aussi.
  • middleware.ts:50 : x-nonce est aussi renvoyé dans la réponse, alors que seul l'en-tête de requête sert.
  • Sentry : dataCollection ne couvre que la collecte automatique. captureError(..., headers: safeHeaders(req.headers)) (middleware.ts:97) envoie toujours x-forwarded-for / x-real-ip. C'était déjà le cas avant, mais ça contredit « en-têtes non collectés » ; une liste blanche serait plus sûre.
  • next.config.js:116 : debug: true est resté sur le plugin de build Sentry.

Nits

  • page.tsx:8 : force-dynamic est inutile, le layout racine appelle déjà headers().
  • common/config.ts:11 (hors diff) : le getter nonce renvoie le SHA git, un « nonce » public et statique, utilisé nulle part. À supprimer pour éviter le piège.

Bien vu : nonce via crypto.randomUUID() posé sur la requête et la réponse, en-têtes entrants écrasés côté middleware et supprimés côté nginx, nonce repris par react-dsfr, Matomo et les scripts Next, pas de double CSP, migration Sentry 11 propre (options obsolètes retirées, sourcemaps masquées), tests sur l'en-tête falsifié et les booléens invalides.

@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 09:54 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 09:54 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 09:54 — with GitHub Actions Active
@LucasCharrier
LucasCharrier deployed to build-review-auto October 5, 2026 09:54 — with GitHub Actions Active
@LucasCharrier

Copy link
Copy Markdown
Contributor Author

Merci pour la relecture. Tout est traité dans 8c86241…3f8f94a9f.

Important

  • Dependabot : bien vu, seul le fichier d'alpha est lu. Le changement est retiré de cette PR.
  • *.gouv.fr dans script-src : passé à script-src 'self' 'nonce-…' 'strict-dynamic' et base-uri 'self'. 'self' ne sert plus qu'aux navigateurs sans CSP 3. Vérifié sur un build de prod local : tous les scripts portent le nonce, aucune violation CSP en console, et la navigation côté client charge bien ses chunks.
  • EGAPRO_SMTP_SSL en prod : la valeur déployée est 1, vraie pour l'ancien bool() comme pour le nouveau parseur. STARTTLS reste actif.
  • Test e2e : le dernier run est allé jusqu'aux tests, et il échoue pour une raison étrangère à cette PR. test-login ouvre la session sans rattacher le compte de test à son SIREN. clean-test-user ne supprime que les déclarations des SIREN possédés, donc rien, et la déclaration créée par le premier test bloque tous les suivants : le premier test de chaque parcours passe, les autres tombent sur la déclaration existante. test-login synchronise maintenant la propriété comme une connexion ProConnect.

Mineurs

  • config.py : le vocabulaire du parseur est en minuscules, et DEV_FLAVOURS aussi, qui avait le même défaut (EGAPRO_DEV_FLAVOURS pouvait autoriser les secrets de dev hors review app). Test ajouté.
  • test_config.py : les globales réécrites par init() sont restaurées via monkeypatch.
  • nginx : le 426 remonte au niveau server, donc sur toutes les locations, et proxy_set_header Upgrade "" disparaît. Content-Security-Policy, Content-Security-Policy-Report-Only et X-Nonce sont vidés dans chaque location qui proxifie vers Next, via un fichier inclus : une location qui pose ses propres proxy_set_header n'hérite pas de ceux du server. Le middleware supprime aussi -Report-Only.
  • x-nonce n'est plus renvoyé au navigateur, il reste sur la requête.
  • Sentry : liste blanche d'en-têtes (accept, accept-language, content-type, host, user-agent), donc plus d'IP client.
  • debug: true retiré du plugin de build.

Nits

force-dynamic retiré (la page reste rendue par requête au build), getter nonce supprimé.

@Viczei

Viczei commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Review actualisée après lecture des changements jusqu’au commit 3f8f94a9f1687e53ab557a7f611cdcb937594d92.

Contexte de maintenance : master porte la V1 en production, figée hors correctifs de sécurité explicitement décidés ; alpha porte la V2 en développement.

1. Dependabot V1 — point résolu

L’ajout des mises à jour npm hebdomadaires sur master a bien été retiré. Cela correspond à la politique de maintenance de la V1. Il n’y a donc pas lieu de reporter ce bloc sur alpha pour cibler master ; ma recommandation initiale sur ce point est remplacée par ce constat.

2. E2E — correction relue, résultat CI encore attendu

Le lancement précédent sur 18b6340 comptait 11 échecs, 4 réussites et 1 test en attente : les parcours arrivaient sur une déclaration déjà transmise au lieu du formulaire attendu. Les logs et captures suggéraient un nettoyage incomplet des données de test, sans démontrer une régression des correctifs de sécurité.

La route test-login synchronise désormais la propriété du SIREN du compte de test, ce qui permet à la route de nettoyage de retrouver les déclarations à supprimer. La correction est cohérente avec le symptôme observé et un test unitaire a été ajouté. Le nouveau lancement E2E et plusieurs autres contrôles CI sont encore en cours au moment de cette review : leur réussite reste à vérifier avant fusion.

Avis

Review de code approuvée. Aucun autre défaut bloquant démontré dans le périmètre relu, y compris les derniers ajustements CSP, Nginx, Sentry et configuration API. Cette approbation ne vaut pas constat de réussite des contrôles encore en cours. Analyse fondée sur le diff, les sources des dépendances et les logs/captures CI précédents ; suites complètes non relancées localement.

@Viczei Viczei left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review de code approuvée après relecture du commit 3f8f94a9f1687e53ab557a7f611cdcb937594d92 et prise en compte du gel de la V1. Les deux points et leur état sont détaillés ici : #4720 (comment)

L’ajout Dependabot V1 a été retiré et la correction du nettoyage E2E a été relue. Les contrôles CI encore en cours restent à vérifier avant fusion.

@tokenbureau

tokenbureau Bot commented Oct 5, 2026

Copy link
Copy Markdown

🎉 Deployment for commit 3f8f94a :

Ingresses
Docker images
  • 📦 docker pull harbor.fabrique.social.gouv.fr/egapro/egapro/api:sha-3f8f94a9f1687e53ab557a7f611cdcb937594d92
  • 📦 docker pull harbor.fabrique.social.gouv.fr/egapro/egapro/app:sha-3f8f94a9f1687e53ab557a7f611cdcb937594d92
  • 📦 docker pull harbor.fabrique.social.gouv.fr/egapro/egapro/files:sha-3f8f94a9f1687e53ab557a7f611cdcb937594d92
  • 📦 docker pull harbor.fabrique.social.gouv.fr/egapro/egapro/nginx:sha-3f8f94a9f1687e53ab557a7f611cdcb937594d92
  • 📦 docker pull maildev/maildev:2.1.0
  • 📦 docker pull sosedoff/pgweb:0.14.1
Debug

@maxgfr maxgfr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je valide, mais j'ai peur que la montée de version de Sentry casse des trucs au niveau des source maps ou autre, à vérifier

@LucasCharrier
LucasCharrier merged commit f0702d1 into master Oct 5, 2026
18 of 23 checks passed
@LucasCharrier
LucasCharrier deleted the fix/secu-audit-v1-remaining branch October 5, 2026 15:06
@tokenbureau

tokenbureau Bot commented Oct 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 3.18.7 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@tokenbureau tokenbureau Bot added the released label Oct 6, 2026

This branch was successfully deployed

2 active deployments
build-review-auto — 3f8f94a9 Deployed Oct 5, 2026 by LucasCharrier via build-files #6513
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Poursuivre les fix de sécurité pour la V1

3 participants