Skip to content

feat(privacy): PII protection on annotation.notes (regex filter + encryption at rest + audit reads) #367

Description

@Yanstart

Contexte

annotations.notes (String 2000) accepte du texte libre saisi par les pathologistes. Risque RGPD/HIPAA majeur : un pathologiste peut écrire « Patient X, suspicion lymphome » directement. Si la DB est exfiltrée, exportée, ou consultée par un dev en debug, PII en clair.

Référence : docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md — décision D4.

Objectif

Protection multi-couches :

  1. Filtre regex au POST/PATCH : refuse noms/prénoms évidents, NISS, dates de naissance, identifiants dossier
  2. Chiffrement at-rest via cryptography.fernet + clé en secret manager
  3. Audit des lectures : chaque accès au champ notes non-null génère un log
  4. Documentation utilisateur : interdiction explicite du PII, alternative recommandée (référence FHIR)

Approche technique proposée

Filtre regex (Phase 1, déployable seule)

PII_PATTERNS = [
    r'\b\d{13,15}\b',                        # NISS belge / NSS français
    r'\b(M\.|Mme|Dr\.?)\s+[A-Z][a-zÀ-ÿ]+',   # titre + nom
    r'\b\d{2}[/.-]\d{2}[/.-]\d{2,4}\b',      # date format européen
    r'\bpatient\s+(?:n°\s*)?\w+\b',          # référence patient explicite
]

POST/PATCH : si match → 422 avec message pédagogique + suggestion d'alternative.

Chiffrement at-rest (Phase 2)

class EncryptedString(TypeDecorator):
    impl = String
    def process_bind_param(self, value, dialect):
        if value is None: return None
        return Fernet(settings.ENCRYPTION_KEY).encrypt(value.encode()).decode()
    def process_result_value(self, value, dialect):
        if value is None: return None
        return Fernet(settings.ENCRYPTION_KEY).decrypt(value.encode()).decode()

notes: Mapped[str | None] = mapped_column(EncryptedString(2000), nullable=True)

Clé ENCRYPTION_KEY stockée :

  • Dev : env var (peut être fixe pour dev)
  • Prod : Vault / AWS Secrets Manager / Azure Key Vault

Audit lectures (Phase 3)

Modifier routes/annotations.py::get_annotation : si la réponse contient un notes non-null, log audit READ_PII annotation={id}.

Acceptance criteria (fonctionnel)

  • Module backend/services/privacy/pii_filter.py avec regex + tests
  • Validation enforced sur POST/PATCH /api/v1/annotations — 422 si PII détecté
  • EncryptedString TypeDecorator dans models/_types.py
  • notes chiffré en DB (validé par SELECT direct via psql)
  • Audit log READ_PII généré sur lecture de notes non-null
  • Migration 012_encrypt_notes.py (best effort + flag)
  • Documentation docs/Manuel/06-ANNOTATIONS.md (section RGPD)

Review checklist (conditions de validation pour le reviewer PR)

Code & sécurité

  • Tests unitaires regex : 1 par pattern × cas positif/négatif (≥ 16 tests)
  • Faux positifs documentés et traités (ex : « Dr. House » comme nom de série, pas un médecin)
  • Tests : valeur encryptée en DB ≠ valeur claire (SELECT direct)
  • Rotation de clé documentée (docs/Admin/services/secrets-rotation.md)
  • Clé hors du repo (test : git log -p .env* doit être vide d'ENCRYPTION_KEY)

Compliance

  • Référence RGPD art. 32 (sécurité du traitement) dans la doc
  • DPIA envisagée (issue séparée ou TODO documenté pour le DPO du CHU)
  • Logique de bypass admin pour debug : explicitement absente (ou loggée)

UX

  • Message 422 explicite et pédagogique (FR + EN)
  • Suggestion d'alternative claire dans le message
  • Pas de fuite du contenu détecté dans la réponse d'erreur (juste « PII detected »)

Performance

  • Overhead chiffrement < 5 ms par annotation à l'écriture
  • Pas de N+1 sur les listes (déchiffrement en bulk)

Documentation

  • docs/Manuel/06-ANNOTATIONS.md mis à jour avec section RGPD claire
  • docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md : D4 marqué RESOLVED
  • Notification au DPO mentionnée dans le CHANGELOG

Dépendances

Hors-scope

  • Anonymisation rétroactive d'annotations historiques contenant du PII
  • ML-based PII detection (NER) — phase 4 envisageable
  • Externalisation complète vers FHIR Observation (issue séparée)

Références

  • docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md §4 (D4)
  • RGPD art. 32 — sécurité du traitement
  • HIPAA Safe Harbor — 18 identifiants à exclure
  • cryptography.fernet doc

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions