Protection des Données Personnelles — Flux IA

Résumé exécutif

Ce document est le résultat d'un audit technique du code source réalisé en Sprint 9. Il cartographie précisément les données personnelles des candidats transmises aux fournisseurs LLM tiers (OpenAI, Anthropic, Google Gemini). Quatre flux critiques ont été identifiés transmettant des données non pseudonymisées. Ces flux constituent des GAPs de conformité RGPD Art. 5(1)(c), Art. 25 (privacy by design) et Art. 28 (sous-traitance).


Contexte réglementaire

  • RGPD Art. 5(1)(c) — Minimisation des données : seules les données strictement nécessaires peuvent être traitées.
  • RGPD Art. 25 — Protection des données dès la conception : pseudonymisation doit être implémentée par défaut.
  • RGPD Art. 28 — Contrats de sous-traitance : les LLMs tiers sont des sous-traitants ; transfert de PII non pseudonymisées sans base légale explicite est illicite.
  • RGPD Art. 44-49 — Transferts hors UE : OpenAI, Anthropic et Google sont des entités américaines ; les données transmises constituent des transferts internationaux nécessitant des garanties (SCCs).
  • AI Act Art. 10 — Gouvernance des données pour systèmes à haut risque : qualité et minimisation des données d'entrée.

Références légales


Audit des flux — Tableau GAP

#FluxFichierLigneDonnées personnelles exposéesProtection activeSévéritéStatut
1Matching candidat vs offresrc/inngest/functions/matching.tsL.140–144firstName, lastName, email, cityAndCountry, currentJobTitle, languages, expériencesReversible PII MaskingFAIBLEConforme
2Parsing/conversion CVsrc/features/analyzis/lib/convert-resume.tsL.30–57CV brut complet : nom, prénom, email, téléphone, adresse physique, GitHub, LinkedInReversible PII MaskingFAIBLEConforme
3Matching Express V2 (Inngest)src/inngest/functions/matching-express.tsL.30JSON.stringify(cvData, null, 2) — objet candidat complet sérialiséFiltrage des clés strictFAIBLEConforme
4Génération rapport matchingsrc/features/analyzis/lib/express-matching-report.tsL.175–177options.resumeText injecté dans le prompt (contient nom, email du flux 1)Reversible PII MaskingFAIBLEConforme

Historique des GAPs (Corrigés par l'implémentation du Reversible PII Masking)

GAP 1 — matching.ts : construction de candidateText avec PII

Fichier : src/inngest/functions/matching.ts, lignes 140–144

Code observé lors de l'audit Sprint 9 :

  • Nom: ${candidate.firstName} ${candidate.lastName} — transmis en clair
  • Email: ${candidate.email} — transmis en clair
  • Localisation: ${candidate.cityAndCountry} — transmis en clair

Ce texte (candidateText) est ensuite passé intégralement à generateMatchingExpressReport({ resumeText: candidateText }) qui l'injecte dans le prompt LLM.

Impact : Le prénom, le nom et l'adresse email du candidat sont transmis en clair aux APIs OpenAI/Anthropic/Google.

Correction apportée : Application de l'utilitaire maskSpecificPII (src/features/ai-ethics/pii-utils.ts) qui remplace les champs d'identification directe (nom, prénom, email, téléphone) par des balises génériques de type [FIRST_NAME_MASKED], [EMAIL_MASKED] avant la construction du texte.


GAP 2 — convert-resume.ts : CV brut en prompt

Fichier : src/features/analyzis/lib/convert-resume.ts

Le RESUME_PROMPT_TEMPLATE et RESUME_PROMPT_QUICK injectent {resumeText} directement dans le prompt. Le mode quick extrait explicitement lastName, firstName, email. Le texte brut du CV (contenant l'ensemble des PII du candidat) est injecté sans prétraitement.

Impact : Toutes les données personnelles présentes dans le CV (nom, email, téléphone, adresse, profils LinkedIn/GitHub) sont transmises aux APIs LLM.

Correction apportée : Implémentation du Reversible PII Masking via src/lib/pii-masker.ts. Le texte du CV est prétraité avec maskPII() qui détecte et remplace les emails, numéros de téléphone, adresses physiques et liens (LinkedIn, GitHub) par des tokens réversibles (ex: [[EMAIL_1]], [[PHONE_1]]). Lors de la réponse du LLM, la fonction unmaskPII() restaure les données originales pour l'affichage en base de données sans que le LLM n'ait jamais vu les valeurs réelles.


GAP 3 — matching-express.ts : cvData JSON sérialisé

Fichier : src/inngest/functions/matching-express.ts, ligne 30

JSON.stringify(cvData, null, 2) est inséré dans le prompt sans filtrage. L'objet cvData contient l'intégralité du profil candidat.

Impact : L'intégralité du profil candidat (tous les champs) est exposée au LLM tiers.

Correction apportée : Les données du profil candidat sont désormais expurgées des éléments identifiants directs ou masquées selon la même logique de préservation de la pseudonymisation, via le respect strict du filtrage des clés transmises au prompt.


GAP 4 — express-matching-report.ts : resumeText non filtré

Fichier : src/features/analyzis/lib/express-matching-report.ts, ligne 175

options.resumeText provient directement du flux GAP 1 (matching.ts) avec nom/email intégrés, et est injecté tel quel dans le prompt.

Correction apportée : options.resumeText est préalablement nettoyé avec maskPII(options.resumeText) (via src/features/ai-ethics/pii-utils.ts) avant toute injection textuelle dans les instructions du LLM.


Protections existantes dans le code (confirmées par audit)

MesureFichierChamps couvertsStatut
Anonymisation RGPDsrc/app/api/public/rgpd/anonymize/route.ts + .../rgpd/server/routers.tsfirstName, lastName, email, phoneNumber, addressActif (post-demande)
Reversible PII Maskingsrc/lib/pii-masker.tsemail, phoneNumber, address, linksActif (pré-traitement LLM)
Static PII Maskingsrc/features/ai-ethics/pii-utils.tsfirstName, lastName, email, phoneNumberActif (pré-traitement LLM)
Chiffrement AES-256 au repossrc/lib/encryption.ts (encrypt/decrypt)email, phoneNumber, address en base PostgreSQLActif (stockage)
Historique consentementsmodèle ConsentHistoryINITIAL, UPDATE, WITHDRAWAL, MANUAL + ipAddressActif
Purge planifiéedeletionScheduledAt / deletionCompletedAt sur CandidateActif
Multi-tenancyorganizationId dans toutes les requêtes PrismaIsolation organisationsActif

Note : Le chiffrement AES-256 protège les données au repos dans PostgreSQL. Le Reversible PII Masking protège activement les données en transit vers les APIs tierces.

Anonymisation partielle : Les champs githubLink, linkedinLink, personnalWebsiteLink, cityAndCountry, languages ne sont pas effacés lors de l'anonymisation RGPD actuelle.


Plan de remédiation recommandé

PrioritéActionEffort estimé
P1 — CRITIQUEImplémenter le Reversible PII Masking (pii-masker.ts) et le routage des réponses2 jours Terminé
P1 — CRITIQUEAppliquer l'obfuscation statique (maskSpecificPII) dans matching.ts1 jour Terminé
P2 — HAUTEFiltrer statiquement les PII résiduelles dans express-matching-report.ts avec pii-utils.ts1 jour Terminé
P2 — HAUTESigner DPAs avec Anthropic, Google (OpenAI déjà signé)2 jours Terminé
P3 — MOYENNECompléter l'anonymisation RGPD "droit à l'oubli" : ajouter githubLink, linkedinLink, cityAndCountry aux champs effacés physiquement en base.0.5 jour

User Stories couverts

  • US-166 — Minimisation données : audit champs, rapport
  • US-164 — Cartographie traitements données
  • US-165 — Gestion bases légales

Responsables


Historique des versions

VersionDateAuteurModifications
1.0.02026-03-23K. MsaoubiCréation — audit code Sprint 9