Skip to content

Securite et RGPD

IlanAMG edited this page Oct 14, 2026 · 14 revisions

Sécurité et RGPD

Statut : 🟡 Brouillon · Màj : 2026-10-14 · Tickets : #306 #397

Cette page fixe les exigences produit. Les consignes de travail qui en découlent (niveaux de risque des changements, revue OWASP du diff, interventions en production) sont dans CLAUDE.md du dépôt ; SECURITY.md ne porte que la procédure de signalement d'une vulnérabilité.

Actifs et menaces

Widoo est une application grand public avec des comptes, du contenu créé par la communauté et des données de sortie.

Actifs : la base, dont les comptes utilisateurs (identité, e-mail, favoris, programmations, historique de sorties, position implicite) ; le contenu créé et les médias (parcours, photos) ; l'intégrité du catalogue de lieux (une fermeture non détectée casse la promesse) ; la disponibilité de la recherche ; les clés des services tiers facturables.

Sont non fiables : les entrées de l'app, les URL, les fichiers envoyés et les textes libres.

Menaces réalistes : vol de compte, contournement des rôles (publier sans modération, éditer le lieu d'un autre), spam de création ou de signalements, injection via textes libres, fuite de secret, abus des API tierces payantes (Mapbox) ou de l'envoi d'e-mails (Brevo : quota, boîtes de tiers inondées) via l'app, upload de contenu illicite, scraping massif du catalogue.

Propriétés obligatoires

  • Jeton Firebase vérifié à chaque requête authentifiée ; rôle relu en base, jamais déduit du jeton.
  • Contrôle d'accès par ressource : un utilisateur n'écrit que ses parcours, lieux et programmations ; les statuts de modération ne sont modifiables que par moderator et admin.
  • Validation Zod de toute entrée et de toute sortie de l'API, refus par défaut en cas d'échec ; textes libres nettoyés (longueur, pas de HTML), URL de réservation limitées à https et à une liste de domaines connus ou marquées « lien externe non vérifié ».
  • Requêtes paramétrées via Drizzle ; aucune concaténation SQL.
  • Limitation de débit : recherche 60/min par IP, création de lieu 20/jour par compte, signaux 30/jour par compte, upload 50/jour, lien de connexion un envoi par adresse toutes les 30 s et 5/heure, 30/heure par IP (D-090).
  • Clés tierces : les clés publiques embarquées (Mapbox, Firebase) sont restreintes aux bundle ids et aux URL ; Directions et Geocoding passent par l'API avec limitation de débit, jamais depuis l'app pour les usages facturables ; Geocoding sans cache serveur au MVP (résultats temporaires de Mapbox, qu'il est interdit de conserver, D-081).
  • Upload : URL signées, type et taille vérifiés (10 Mo max), redimensionnement serveur qui réencode l'image (supprime les métadonnées EXIF, dont la position GPS).
  • Secrets dans Secret Manager. Aucun secret, jeton, cookie ni donnée personnelle réelle (e-mail, nom, photo…) dans Git, le code de l'app, les tests, les logs, les prompts, rapports et transcriptions d'agents ou les sorties de commande ; les recettes utilisent des données fictives.
  • Staging et production séparés (projets ou instances distincts), données fictives en staging ; voir Architecture-Technique › Environnements.
  • Logs structurés sans donnée personnelle ; identifiant utilisateur opaque uniquement.
  • Migrations Drizzle additives, versionnées et testées.
  • Sauvegardes Cloud SQL quotidiennes, PITR 7 jours, restauration testée avant lancement.
  • Dépendances : Dependabot, audit en CI. Une alerte bloque si elle est critique ou élevée, ou moyenne avec un impact plausible sur l'accès, les secrets ou les données ; une alerte faible devient une dette suivie en issue.

Données personnelles

Donnée Finalité Base légale Durée
E-mail, prénom, avatar compte, notifications contrat vie du compte + 30 jours
Identifiant Firebase authentification contrat idem
Favoris, programmations, historique de parcours service contrat idem ; historique purgeable par l'utilisateur
Position de l'appareil centrage de la carte, tri par distance consentement (permission OS) jamais stockée côté serveur
Jeton push rappels consentement jusqu'à révocation
Signaux de vérification qualité du contenu intérêt légitime 24 mois, puis anonymisés
Événements analytics amélioration produit consentement (bannière au premier lancement, refus possible) 12 mois, PostHog UE
Crash reports fiabilité intérêt légitime 90 jours
Langue, préférences de notification (D-062) service contrat vie du compte + 30 jours
Changement d'e-mail en attente (D-062) confirmer la nouvelle adresse contrat jusqu'à confirmation, annulation ou expiration du lien
Messages au support (D-063) répondre intérêt légitime, contrat à fixer (3 ans après le dernier échange proposés)
  • Aucune position n'est envoyée au serveur : la recherche reçoit une zone de carte, pas la position exacte ; le tri par distance est calculé côté client.
  • Photos : l'utilisateur atteste des droits ; pas de reconnaissance faciale ; suppression immédiate à la demande.
  • Droits : accès, rectification (profil), effacement (suppression de compte dans l'app), portabilité (export JSON dans Réglages › Confidentialité, E-19, D-063).
  • Mineurs : service réservé aux 16 ans et plus (CGU) ; pas de vérification d'âge au MVP.
  • Sous-traitants : Google Cloud (UE), Firebase, Mapbox, PostHog (UE), Sentry (UE), Expo. Registre des traitements à tenir ; politique de confidentialité et conditions d'utilisation rédigées en brouillon le 2026-09-28 (Politique-de-confidentialite, Conditions-d-utilisation, D-063), à relire par un juriste avant la soumission aux stores. À ajouter à la liste : RevenueCat (abonnements), le fournisseur de traduction automatique des parcours et le prestataire d'envoi d'e-mails.
  • Réglages obligatoires des sous-traitants, à vérifier sur chaque nouvelle organisation ou nouveau projet :
    • PostHog : organisation et projets sur PostHog Cloud UE (eu.posthog.com), « Discard client IP data » activé ; l'assistant PostHog AI n'est pas utilisé.
    • Sentry : organisation en région UE (DSN en .de.sentry.io) ; « Require Data Scrubber », « Require Using Default Scrubbers » et « Prevent Storing of IP Addresses » activés au niveau de l'organisation ; au niveau de l'organisation également, règles avancées « Remove » sur contexts.app.device_app_hash (empreinte d'appareil ajoutée par le SDK natif iOS) et $user.geo.** (ville et pays que Sentry déduit de l'IP de l'émetteur avant de la jeter), qui s'appliquent d'office à tout nouveau projet. Vérifié le 2026-09-24 sur widoo-mobile (#108) et widoo-api (#109).
  • Toute nouvelle donnée personnelle, table, export ou sous-traitant revient au cadrage avant tout code : finalité, durée, information de la personne, transfert éventuel ; le tableau ci-dessus est mis à jour en conséquence.

Modération et contenu

Charte dans Creation-et-Moderation. Signalement d'un parcours pour contenu inapproprié disponible depuis la fiche (motif, traité dans A-02). Journal immuable des actions de modération.

Risques acceptés

Un risque résiduel qu'Ilan accepte lors d'une PR de niveau 2 ou 3 est consigné dans cette section, et le Design-Changelog l'annonce. Une entrée par risque, au format suivant :

### <Risque, en une ligne>
- **Contexte :** …
- **Raison de l'acceptation :** …
- **PR :** [#N](https://github.com/Inprogress-Agency/widoo-app/pull/N)
- **Accepté par :** …
- **Date :** AAAA-MM-JJ
- **Date de révision :** AAAA-MM-JJ

Révocation des jetons Firebase non vérifiée

  • Contexte : l'API vérifie la signature, l'audience, l'émetteur et l'expiration de chaque jeton, mais pas sa révocation (verifyIdToken sans checkRevoked). Un compte désactivé ou déconnecté depuis la console Firebase garde l'accès jusqu'à l'expiration de son jeton, une heure au plus.
  • Raison de l'acceptation : vérifier la révocation coûte un appel à Firebase à chaque requête ; un compte supprimé par l'utilisateur est refusé immédiatement par la base (users.deleted_at).
  • PR : #95
  • Accepté par : Paul, par délégation d'Ilan
  • Date : 2026-09-24
  • Date de révision : avant la soumission aux stores

Staging : un seul utilisateur de base aux droits larges

  • Contexte : en staging, l'API et le job de migration utilisent le même utilisateur Postgres intégré (cloudsqlsuperuser), qui peut modifier le schéma à l'exécution.
  • Raison de l'acceptation : staging seulement, données fictives ; un rôle de migration et un rôle d'exécution séparés sont à créer pour la production.
  • PR : #111
  • Accepté par : Paul, par délégation d'Ilan
  • Date : 2026-09-24
  • Date de révision : avant la mise en place de la production

Staging : TLS vers Cloud SQL sans vérification du certificat

  • Contexte : l'URL de la base utilise sslmode=require : la connexion est chiffrée (l'instance refuse le clair) mais le client ne vérifie pas le certificat du serveur ; elle ne passe que par le réseau privé du projet.
  • Raison de l'acceptation : instance sans IP publique, joignable seulement depuis le VPC ; vérification du certificat (CA de l'instance) à ajouter pour la production.
  • PR : #111
  • Accepté par : Paul, par délégation d'Ilan
  • Date : 2026-09-24
  • Date de révision : avant la mise en place de la production

Staging : sortie VPC directe au lieu du connecteur VPC

  • Contexte : le ticket #24 prévoyait un connecteur VPC ; Cloud Run utilise la sortie VPC directe vers le sous-réseau widoo-run.
  • Raison de l'acceptation : même accès privé à Cloud SQL, sans les 10 € par mois du connecteur.
  • PR : #111
  • Accepté par : Paul, par délégation d'Ilan
  • Date : 2026-09-24
  • Date de révision : avant la mise en place de la production

Audit des dépendances : deux alertes élevées sans correctif ignorées (node-forge, braces)

  • Contexte : exception nommée à la règle des alertes de Propriétés obligatoires, qui reste en place. Depuis le 2026-10-05, pnpm audit --audit-level=high échoue sur toutes les PR du dépôt à cause de deux alertes élevées sans version corrigée en amont :

    • node-forge 1.4.0, GHSA-86w9-cpqp-85rv (CVE-2026-85393, CVSS 7.5) : la vérification de signature RSA PKCS#1 v1.5 accepte des éléments DigestAlgorithm imbriqués en trop, ce qui permet de forger une signature sur une clé à exposant public faible (e=3). N'entre que par apps/mobile > expo > @expo/cli.
    • braces 3.0.3, GHSA-vfj7-8cjw-p6xm (CVE-2026-93687, CVSS 7.5) : déni de service par épuisement de pile sur un motif très imbriqué. Entre par tailwindcss > chokidar et par micromatch (Metro, Tailwind, ESLint).

    Aucun des deux n'atteint l'app livrée (absents des source maps des bundles iOS et Android exportés) ni l'image de l'API (absents de la sortie copiée dans l'image au rejeu de la construction). Exposition : postes de développement, CI, build EAS.

  • Raison de l'acceptation : au pire, le plantage d'un outil de développement. @expo/cli ne vérifie avec node-forge aucune signature venue de l'extérieur : il signe et vérifie ses propres manifestes, avec des clés en e=65537, et le projet n'utilise ni expo-updates ni signature de code. Les motifs donnés à braces viennent de la configuration du dépôt. Aucun contournement : node-forge 1.4.0 et braces 3.0.3 sont les dernières versions publiées, @expo/cli (57.0.27 comme 58.1.2) et Tailwind 3 en dépendent toujours, et les correctifs proposés en amont ne sont pas fusionnés (digitalbazaar/forge#1152, micromatch/braces#75).

    • Portée : ces deux identifiants seulement (auditConfig.ignoreGhsas dans pnpm-workspace.yaml) ; l'audit continue de bloquer toute autre alerte élevée ou critique.
    • Limites : pnpm ne sait ni restreindre l'exception à un chemin de dépendance ni lui donner une date d'expiration. La révision se tient donc à la main, par le ticket #280. Un identifiant ignoré l'est aussi si le paquet arrive par un autre chemin, par exemple par le site web en cours de développement.
    • Écarté : attendre les correctifs en bloquant toutes les fusions, sans date ; --ignore-unfixable, qui ignorerait toute alerte sans correctif, présente ou future ; baisser le seuil de l'audit.
  • PR : #279 (ticket de l'exception ; PR à venir, à compléter à la fusion)

  • Accepté par : Paul Thiberville, par délégation d'Ilan

  • Date : 2026-10-05

  • Date de révision : 2026-11-05 (#280), ou plus tôt dès la publication d'une version de node-forge > 1.4.0 ou de braces > 3.0.3. Condition de retrait, alerte par alerte : la version corrigée est publiée et atteignable par les dépendances du dépôt (au besoin par une version d'@expo/cli ou de Tailwind qui l'accepte, ou par un overrides de pnpm) ; l'identifiant sort alors de ignoreGhsas et l'audit repasse sans lui.

Manques connus, qui ne sont pas des risques acceptés, à combler avant la soumission aux stores :

  • la réinscription après suppression est permise (décision d'Ilan, D-027), mais le code refuse encore la même identité jusqu'à la purge : #128 corrige ;
  • la purge des comptes supprimés à 30 jours et la suppression du compte Firebase ne sont pas implémentées (durées du tableau Données personnelles) ;
  • le consentement à la mesure d'audience ne peut pas encore être retiré dans l'app : le retrait est dessiné (interrupteur « Mesure d'audience » des Réglages, E-09, avec ou sans compte, D-060) mais pas implémenté (#107, #188) ;
  • la politique de confidentialité reste à rédiger et à publier : la sheet de consentement du premier lancement (E-18, D-060) et les Réglages y renvoient (#107).

Des risques résiduels sont encore déclarés dans les PR de l'epic #13, en attente de décision : #91 (fusionnée), #96 et #97 (gardes d'authentification déclarées route par route), #107 à #109, #112.

Avant lancement

Revue de sécurité sur commit figé, test de restauration, politique de confidentialité et CGU publiées, formulaire de contact RGPD, vérification des permissions demandées par l'app (position « quand l'app est utilisée » seulement, notifications, photos).

Clone this wiki locally