Aller au contenu

Incident response — atlas

Procédure à appliquer en cas de suspicion ou de confirmation d’incident de sécurité touchant le code source atlas, les unités déployées — apps SvelteKit amarre, ecrin, find-an-expert, sillage, atlas-dashboard, crf-dashboard et le service Hono crf (API REST au-dessus de REDCap) — les secrets, ou les données accédées par ces unités (REDCap, OpenAlex, Appwrite). La source de vérité du périmètre déployé est la matrice d’images de .github/workflows/images.yml (images publiées sur le registre de conteneurs GitHub, GHCR — GitHub Container Registry).

Ce document est un runbook opérationnel, pas une politique de conformité. Il s’utilise au moment de l’incident, ouvert dans un onglet en parallèle de la console GitHub, de l’admin Appwrite et du gestionnaire de secrets. Garder court, concret, navigable.

Choisir la sévérité dès la détection — elle conditionne le tempo de la réponse et les notifications.

NiveauCritèresDélai d’engagement
P0Fuite confirmée de données personnelles ; compromission active d’un compte admin Appwrite ; exfiltration de tokens REDCap valides ; supply-chain compromise sur un package ou une image atlas publié(e).Action immédiate, 24/7.
P1Vulnérabilité exploitable à distance non corrigée en prod ; secret commité par erreur (token, clé) ; détection d’un publish sans provenance valide ; image GHCR non signée, signée par une identité inattendue ou dont la provenance SLSA (Supply-chain Levels for Software Artifacts, attestation de chaîne de build) est invalide (ADR 0069) ; CodeQL error sur une route publique.Action sous 4 h, heures ouvrées.
P2CodeQL warning haute sévérité ; finding ZAP medium sans exploit immédiat ; advisory CVE moderate sur une dépendance prod ; tentative de brute-force détectée mais bloquée par rate-limit.Action sous 48 h, heures ouvrées.
P3Hygiène : alertes Dependabot low, faux positif à dismisser, finding ZAP info, hardening cosmétique.Triage hebdomadaire.

En cas de doute, escalader d’un cran par défaut.

Sources à monitorer pour repérer un incident.

SourceQuoi y chercherPériodicité
Security → Code scanningNouveaux findings CodeQL error ou high warningÀ chaque PR + post-merge
Security → Code scanning (Trivy SARIF)Vulnérabilités CRITICAL/HIGH remontées par le scan Trivy des images conteneur (rapport SARIF — Static Analysis Results Interchange Format) ; faux positifs gérés via .trivyignore versionné (ADR 0069)À chaque build d’image (images.yml)
Security → DependabotAdvisories high ou criticalQuotidien (auto-merge couvre patches)
Security → Secret scanningSecrets détectés ; push protection bloquésQuotidien
Actions → GitleaksFindings sur PR ou push mainÀ chaque PR
Actions → ZAP BaselineFindings High/Medium après scanSur demande (déclenchement manuel)
Logs Appwrite (console admin)5xx > seuil ; tentatives auth échouées ; latence p95 anormaleÀ cadrer avec les admins infra (cf. § Logs)
Email de divulgation publié dans SECURITY.mdDivulgation responsable externeSurveillance quotidienne
GitHub Private Vulnerability ReportingRapport coordonnéNotification GitHub
  • Ouvrir une issue privée via Security advisories → New draft. Une issue publique exposerait la vulnérabilité avant correctif.
  • Consigner dès l’ouverture : qui a détecté, quand, source, premiers symptômes, sévérité estimée.
  • Ne rien remédier avant l’étape 3.2 — d’abord contenir, ensuite réparer.

Couper l’exposition sans détruire les preuves.

Si compromission d’un secret (token, clé, mot de passe) :

  1. Rotation immédiate côté émetteur (REDCap, Appwrite Console, OpenAlex, GitHub PAT, npm token…) — cf. section Secrets de Sécurité pour la procédure par secret.
  2. Révocation de l’ancien secret (et pas seulement remplacement) si l’émetteur le permet.
  3. Mise à jour des GitHub Secrets / Appwrite variables d’environnement.
  4. Redéploiement des apps qui consomment le secret (amarre, ecrin, find-an-expert, sillage).

Si compromission d’un compte utilisateur Appwrite :

  1. Désactiver le compte côté Appwrite Console (Users → <user> → Block).
  2. Invalider les sessions actives (Users → <user> → Sessions → Delete all).
  3. Auditer les actions récentes du compte via les logs Appwrite.

Si compromission d’un package npm publié :

  1. npm deprecate @univ-lehavre/atlas-<name>@<version> "<message>" immédiatement (le package reste accessible mais signalé).
  2. Vérifier la provenance des autres versions via npm audit signatures et npm view <pkg>@<version> --json | jq .dist.attestations.
  3. Ne pas unpublish sans coordination — npm unpublish est irréversible après 72h et casse les consommateurs en aval.
  4. Préparer une version patch qui surclasse la version compromise.

Si compromission d’une image conteneur publiée sur GHCR (ADR 0069) :

  1. Vérifier la signature cosign (signature keyless via OIDC — OpenID Connect —, sans clé persistante) de l’image suspecte — l’identité signataire attendue est le workflow images.yml du dépôt : cosign verify ghcr.io/univ-lehavre/atlas-<name>:<tag> --certificate-identity-regexp '.../\.github/workflows/images\.yml@.*' --certificate-oidc-issuer https://token.actions.githubusercontent.com. Une identité inattendue ou une signature absente confirme l’incident.
  2. Vérifier la provenance SLSA (provenance:true) et le SBOM CycloneDX (sbom:true, Software Bill of Materials) attachés à l’image pour confirmer la chaîne de build.
  3. Empêcher le pull du tag compromis : le cluster consommateur (ADR 0043) ne doit plus réconcilier ce tag ; le garde-fou de cible au déploiement (ADR 0073) refuse déjà :dev/:latest et un contrat manquant en prod.
  4. Reconstruire et republier une image saine depuis main (build CI vert, scan Trivy propre, nouvelle signature) et basculer le cluster dessus.

Si fuite de données personnelles confirmée :

  1. Couper l’accès au système qui fuite (mettre l’app en maintenance).
  2. Notifier la Admins infra dans les 4 h (cf. § Contacts).
  3. Démarrer le compteur CNIL 72 h (cf. § 3.6 RGPD).

Identifier et supprimer la cause racine.

  • Compromission de secret : trouver comment il a fuité (git log -p, gitleaks detect --log-opts="--all", logs CI, accès workstation).
  • Vulnérabilité applicative : corriger en code, ajouter un test de régression, déployer.
  • Account takeover : forcer un reset de mot de passe (Appwrite) + examiner les autres comptes du même tenant.
  • Dependency vulnerability : bump du package, override transitive si pas encore patché upstream (cf. pnpm.overrides dans package.json), changeset patch.

Restaurer le service à un état sain.

  • Redéployer les apps depuis main (build CI vert obligatoire).
  • Restaurer les données depuis sauvegarde si altérées (cf. § Sauvegardes).
  • Surveiller les métriques (5xx, latence p95) pendant 24 h post-restauration pour détecter une récidive.
  • Lever progressivement les protections temporaires (rate-limits durcis, IP banlists) si elles ont été mises en place pendant le confinement.

Documenter pour ne pas répéter.

  • Dans les 72 h après résolution (P0/P1) ou la semaine suivante (P2).
  • Format : section ajoutée à la fin de ce document (## Post-mortem YYYY-MM-DD — titre court), ou doc séparé docs/security/post-mortems/ si la liste grossit.
  • À couvrir : timeline factuelle, impact, cause racine technique, contribution humaine (process, monitoring), actions correctives (techniques + process) avec owners et dates.
  • Blameless : critiquer le système, pas les personnes. Le but est d’améliorer le process, pas de désigner un coupable.

Si l’incident touche des données à caractère personnel (au sens RGPD article 4) :

  • Notification CNIL dans les 72 h après détection, sauf si le risque pour les personnes est négligeable. Formulaire en ligne : https://www.cnil.fr/fr/notifier-une-violation-de-donnees-personnelles
  • Notification aux personnes concernées si risque élevé.
  • Coordination obligatoire avec la Admins infra (responsable de traitement) — eux ouvrent la notification, pas le mainteneur du repo.
RôleCanalQuand
Mainteneur atlasVoir SECURITY.mdPremier point de contact
Admins infracoordonnées à compléter par le mainteneurP0 confirmé ; suspicion de fuite RGPD ; question infra Appwrite/REDCap
CNIL (si fuite données perso)https://www.cnil.fr/fr/notifier-une-violation-de-donnees-personnellesSous 72 h après détection ; via les admins infra, pas directement
Divulgation responsable externeGitHub PVR ou contact publié dans SECURITY.mdDocumenté dans SECURITY.md
GitHub Securityhttps://github.com/contact/report-contentCompromise de compte / abuse
npm Securityhttps://www.npmjs.com/supportCompromise de package publié
Vanderbilt REDCap (si bug REDCap upstream)https://projectredcap.org/contact/Bug confirmé dans REDCap, pas dans atlas

État actuel : framework documenté ci-dessous, intégration concrète en attente d’arbitrage avec la Admins infra.

SourceAccèsRétentionQuoi y trouver
Appwrite ConsoleAdmins de l’instance Appwriteà confirmer admins infraRequêtes API, auth events, 5xx, audit log admin
Appwrite Sites runtimeIdemà confirmer admins infraLogs SSR SvelteKit (console.error dans hooks.server.ts, etc.)
GitHub ActionsPublic sur PR, repo admin sur main90 joursCI/CD, scans CodeQL/ZAP/SBOM/Gitleaks
GitHub Audit logAdmin org90 jours (free) / 6 mois (enterprise)Push, modifs settings, ajout/retrait collaborator
REDCap upstreamAdmins de l’instance REDCapSelon politique des admins infraRequêtes API utilisant les tokens REDCap

À cadrer avec la Admins infra :

  • 5xx > seuil (suggérer : 5 erreurs/min pendant 5 min) → alerte email / Slack.
  • Latence p95 anormale (suggérer : > 2× la baseline 7j sur 10 min) → alerte.
  • Auth fail rate > seuil (suggérer : 10 échecs/min sur la même IP ou même email) → alerte (signal brute-force, déjà partiellement couvert par le rate-limit applicatif).
  • Code scanning : config GitHub envoie déjà notifications email aux watchers du repo.

Les apps amarre/ecrin/find-an-expert/sillage utilisent un magic-link Appwrite (cf. packages/auth/src/). Les événements à tracer côté Appwrite :

  • Création de session (succès/échec)
  • Logout
  • Création de compte (rate-limité côté app)
  • Suppression de compte (ecrin uniquement)

Appwrite log ces événements nativement dans Logs → Sessions. Pas d’agrégation externe pour l’instant.

État actuel : framework documenté, paramètres concrets à confirmer avec la Admins infra — la politique dépend de l’hébergement Appwrite (self-hosted ? Appwrite Cloud ?).

DonnéeLocalisationOwner sauvegarde
Code source atlasGitHub (mirror local des mainteneurs)GitHub + clones locaux
Données utilisateurs Appwrite (comptes, sessions, tables)Appwrite (baas-mongodb-data en self-hosted)Admins infra
Données REDCapInstance REDCapAdmins infra
SecretsGitHub Secrets + Appwrite vars d’env + ~/.config/atlas/ côté workstationÀ documenter par le mainteneur
SBOMs historiquesArtefacts GitHub Actions (90j)Snapshots manuels — cf. section SBOM de Sécurité

À fixer avec les admins infra selon la criticité applicative.

  • RPO (Recovery Point Objective — combien de données on accepte de perdre) : suggérer 24 h pour amarre/ecrin/find-an-expert/sillage.
  • RTO (Recovery Time Objective — délai max de rétablissement) : suggérer 4 h pour amarre (formulaire de mobilité, période active limitée), 24 h pour ecrin/find-an-expert/sillage.

Valeurs à valider par les owners métier ; ces chiffres ne sont pas des engagements contractuels (cf. note SECURITY.md).

À conduire au moins une fois par an, en environnement isolé (sandbox/amarre-sandbox/). Procédure type :

  1. Récupérer la dernière sauvegarde Appwrite (dump MongoDB).
  2. Monter sandbox/amarre-sandbox/ propre.
  3. Restaurer le dump dans le baas-mongodb du sandbox.
  4. Vérifier que les flows amarre fonctionnent contre cette base restaurée (signup → magic link → submit demande).
  5. Documenter le temps réel de restauration → ajuster le RTO si écart significatif avec l’objectif.

Avant de marquer un incident comme résolu :

  • Cause racine identifiée et documentée.
  • Correctif déployé en prod et validé (smoke test).
  • Tests de régression ajoutés (couvre la vulnérabilité spécifique).
  • Secret(s) compromis rotaté(s) et révoqué(s).
  • Sessions / comptes affectés invalidés.
  • Surveillance 24 h post-déploiement sans récidive.
  • Post-mortem rédigé (P0/P1) ou ticket de suivi créé (P2).
  • Notification CNIL effectuée si fuite données perso (via admins infra).
  • Communication aux parties prenantes (utilisateurs / admins infra).
  • Issue/advisory privée fermée ou rendue publique si responsabilité revealed (en accord avec le reporter).

Aucun incident enregistré à ce jour. Premier post-mortem à ajouter ici quand survient.