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 :
- Filtre regex au POST/PATCH : refuse noms/prénoms évidents, NISS, dates de naissance, identifiants dossier
- Chiffrement at-rest via
cryptography.fernet + clé en secret manager
- Audit des lectures : chaque accès au champ
notes non-null génère un log
- 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)
Review checklist (conditions de validation pour le reviewer PR)
Code & sécurité
Compliance
UX
Performance
Documentation
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
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.Objectif
Protection multi-couches :
cryptography.fernet+ clé en secret managernotesnon-null génère un logApproche technique proposée
Filtre regex (Phase 1, déployable seule)
POST/PATCH : si match → 422 avec message pédagogique + suggestion d'alternative.
Chiffrement at-rest (Phase 2)
Clé
ENCRYPTION_KEYstockée :Audit lectures (Phase 3)
Modifier
routes/annotations.py::get_annotation: si la réponse contient unnotesnon-null, log auditREAD_PII annotation={id}.Acceptance criteria (fonctionnel)
backend/services/privacy/pii_filter.pyavec regex + testsPOST/PATCH /api/v1/annotations— 422 si PII détectéEncryptedStringTypeDecorator dansmodels/_types.pynoteschiffré en DB (validé par SELECT direct via psql)READ_PIIgénéré sur lecture denotesnon-null012_encrypt_notes.py(best effort + flag)docs/Manuel/06-ANNOTATIONS.md(section RGPD)Review checklist (conditions de validation pour le reviewer PR)
Code & sécurité
docs/Admin/services/secrets-rotation.md)git log -p .env*doit être vide d'ENCRYPTION_KEY)Compliance
UX
Performance
Documentation
docs/Manuel/06-ANNOTATIONS.mdmis à jour avec section RGPD clairedocs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md: D4 marquéRESOLVEDDépendances
READ_PII)Hors-scope
Références
docs/architecture/ANNOTATION_DATA_MODEL_AUDIT.md§4 (D4)