Aller au contenu

Limites

piighost dé-identifie, il ne rend pas un texte magiquement sûr. Cette page liste les limites connues, leur raison d'être et comment les atténuer. Elle prolonge le modèle de menaces.

Les détecteurs sont au mieux

Un détecteur ne trouve que ce qu'il sait reconnaître. Deux familles se partagent le travail, avec des angles morts différents.

Un détecteur à motif (RegexDetector) reconnaît des chaînes de caractères qui suivent une structure fixe, comme un email, une IP ou une forme de carte bancaire. Il est déterministe sur ces formats et aveugle au reste. Un détecteur NER (Gliner2Detector, SpacyDetector, TransformersDetector) ou LLM (LLMDetector) reconnaît des entités en texte libre, un nom, un lieu, une organisation, mais il en manque. Un nom rare, une orthographe inhabituelle, une entité hors distribution passent en clair vers le LLM.

Une PII non détectée n'est pas dé-identifiée. C'est un enjeu d'ingénierie, pas un défaut conceptuel.

Mitigation : chaîner un détecteur NER et un RegexDetector via le CompositeDetector, pour couvrir à la fois le texte libre et les formats structurés. Charger un modèle NER spécifique à la locale pour une meilleure précision. Voir Étendre PIIGhost.

Un modèle peut tronquer un texte plus long que son contexte

Un modèle NER a une longueur d'entrée maximale. Un texte plus long est tronqué par le modèle, et la fin tronquée n'est jamais analysée, sa PII passe donc en clair. Rien ne vous avertit par défaut.

La limite est celle du modèle, pas du pipeline. Elle s'impose à toute dé-identification adossée à un modèle NER, et piighost fournit de quoi la contourner plutôt que de la subir.

Mitigation : fixer max_chars sur le détecteur NER à la longueur d'entrée sûre du modèle. Avec auto_chunk activé (le défaut), un texte plus long est découpé en morceaux chevauchants analysés séparément puis recollés, si bien que la fin est couverte. Avec auto_chunk désactivé, un texte trop long lève TextTooLongError plutôt que d'être analysé en partie. Pour de très longues entrées, envelopper le détecteur dans un ChunkedDetector.

La couverture linguistique dépend du modèle

L'ensemble des langues qu'un détecteur NER peut couvrir est fixé par le modèle branché. La couverture varie d'un modèle à l'autre, et toutes les langues ne sont pas supportées avec la même précision. Avant de déployer sur une nouvelle locale, lisez la fiche du modèle et exécutez un petit jeu de validation.

Là encore, la limite est celle du modèle, pas du pipeline. Un détecteur à motif ne la connaît pas, un IBAN ou une adresse mail a la même forme dans toutes les langues.

Mitigation : charger un modèle spécifique à la locale, ou combiner plusieurs détecteurs via le CompositeDetector.

Pas de validation par checksum (volontaire)

RegexDetector matche sur la forme seule. Il ne vérifie aucun checksum, pas de Luhn sur les cartes, pas de clé IBAN, pas de clé NIR. C'est délibéré.

Une valeur structurée peut arriver déformée par de l'OCR, un caractère lu de travers. Un validateur par checksum rejetterait alors un IBAN ou un NIR réel mais mal transcrit, et cette PII repartirait en clair vers le LLM. piighost préfère garder un faux positif de forme plutôt que laisser fuiter une vraie valeur abîmée. C'est un choix de sécurité, échouer du côté qui détecte.

La contrepartie est que RegexDetector peut détecter des chaînes qui ont la forme d'une PII sans en être une (une suite de chiffres qui ressemble à une carte). Le coût d'un tel faux positif est bénin, un token de plus. Le coût du faux négatif inverse serait une fuite.

Mitigation : affiner les motifs si les faux positifs de forme gênent une charge précise. Ne pas réintroduire de filtre par checksum en amont d'un texte qui peut venir d'OCR. Si vos entrées sont saisies au clavier et ne passent jamais par de l'OCR, le compromis s'inverse et vous pouvez écrire votre propre détecteur avec validation par checksum, le port AnyDetector est ouvert. Voir Étendre PIIGhost.

Les placeholders peuvent se confondre selon la factory

La factory de placeholder décide de ce qui distingue deux entités. Certaines familles produisent la même sortie pour deux entrées différentes.

  • RedactPlaceholderFactory ramène toute PII sur <<REDACT>>. LabelPlaceholderFactory ramène toute PII d'un même label sur <<PERSON>>. Ces deux familles ne distinguent pas les entités, donc elles ne sont pas réversibles.
  • MaskPlaceholderFactory garde un fragment de la valeur, j***@mail.com. Deux valeurs de forme voisine peuvent se confondre sur un même masque, et un masque peut aussi se confondre avec une vraie valeur dans une réponse d'outil.
  • LabelCounterPlaceholderFactory (<<PERSON:1>>) et LabelHashPlaceholderFactory (<<PERSON:a1b2c3d4>>) donnent un token distinct par entité et se retrouvent dans le texte, donc elles restent réversibles sans ambiguïté.

Mitigation : voir Placeholder factories pour la taxonomie complète et le choix par usage.

La restauration n'est fiable que sous identité

Restaurer une valeur à partir d'un placeholder suppose que le placeholder identifie une entité unique. Deux propriétés se combinent dans le token. Le typage dit de quelle sorte de PII il s'agit, personne, lieu, email. L'identité dit de laquelle il s'agit parmi celles du même type. Chaque factory porte un tag de préservation qui déclare ce que son token garde des deux.

Factory Tag de préservation Token émis Typage Identité Restauration
RedactPlaceholderFactory PreservesNothing <<REDACT>> non non impossible
LabelPlaceholderFactory PreservesLabel <<PERSON>> oui non impossible
MaskPlaceholderFactory PreservesShape j***@mail.com oui partielle ambiguë
LabelCounterPlaceholderFactory PreservesLabeledIdentityOpaque <<PERSON:1>> oui oui fiable
LabelHashPlaceholderFactory PreservesLabeledIdentityOpaque <<PERSON:a1b2c3d4>> oui oui fiable

Sur Patrick et Marie habitent à Paris, la différence se voit tout de suite.

  • Avec LabelPlaceholderFactory, les deux personnes deviennent le même <<PERSON>>. Le type est là, l'identité non, donc rien ne dit lequel des deux tokens valait Patrick.
  • Avec LabelCounterPlaceholderFactory, Patrick devient <<PERSON:1>> et Marie devient <<PERSON:2>>. Chaque token retombe sur une seule valeur, la restauration est sans ambiguïté.

Le middleware PIIAnonymizationMiddleware impose cette contrainte au niveau du type. Il exige une factory PreservesRecognizableIdentity, c'est-à-dire un token unique par entité et reconnaissable dans un texte. Une factory qui ne remplit pas ce contrat est refusée à la construction (UnrecognizableFactoryError). La frontière d'appel d'outil s'appuie sur du remplacement de chaîne, elle a besoin de tokens uniques pour rester réversible.

Mitigation : garder LabelCounterPlaceholderFactory ou LabelHashPlaceholderFactory avec le middleware. Voir Stratégies d'appel outil pour les modes FULL, INPUT, OUTPUT et PASSTHROUGH.

Les PII inventées par le LLM ne sont pas dans le mapping

La restauration fonctionne sur les valeurs vues à l'entrée. Si le LLM hallucine un nom qui n'a jamais figuré dans les messages de l'utilisateur, par exemple en inventant un nom de client plausible, cette PII n'est dans aucun mapping. Elle ne peut donc pas être rattachée à une valeur d'origine.

Le middleware détecte un cas voisin, le placeholder inventé. Si le LLM fabrique un jeton qui ressemble à un placeholder mais n'a jamais été émis, piighost le repère (le token n'a pas de valeur associée) et le refuse par défaut (InventedPlaceholderError, stratégie RAISE). Les stratégies KEEP et DROP existent pour d'autres politiques.

Mitigation : exécuter une étape de re-détection sur la sortie du LLM au niveau applicatif, et décider s'il faut supprimer, signaler ou re-dé-identifier avant l'affichage. Un garde-fou (DetectorGuardRail, LLMGuardRail, ModerationGuardRail) re-vérifie la sortie dé-identifiée et signale une PII résiduelle, à charge pour l'appelant de lever PIIRemainingError.

La mémoire est locale au processus par défaut

InMemoryConversationMemory garde le mapping thread par thread dans un dictionnaire du processus. Rien ne survit à un redémarrage, rien n'est partagé entre processus. Dès que vous passez à l'échelle horizontalement, deux workers ont deux mémoires et deux espaces de placeholders indépendants, donc la même entité peut recevoir deux tokens différents selon le worker qui la traite.

Mitigation : configurer RedisConversationMemory pour partager le mapping entre workers et le faire survivre à un redémarrage. Ce backend peut chiffrer les valeurs et hacher les clés (optionnel, tout ou rien). Le backend en mémoire peut être borné avec max_threads et ttl pour limiter sa croissance dans un processus de longue durée. Voir Sécurité et Déploiement.

Un thread isole le mapping

La mémoire est cloisonnée par thread_id. Deux conversations séparées ne partagent aucun placeholder, ce qui est voulu, mais implique que la même personne dans deux threads reçoit deux tokens sans lien. Le middleware exige un thread_id et ne se rabat pas sur un thread partagé par défaut, pour éviter qu'une conversation ne voie le mapping d'une autre.

Mitigation : propager un thread_id stable et par conversation. Appeler forget_thread pour purger une conversation de la mémoire quand elle n'a plus lieu d'être.

La latence ajoutée n'est pas encore mesurée

Il n'existe pas de benchmark officiel de la latence ajoutée par le pipeline sur des charges typiques. Le surcoût dépend du détecteur (inférence du NER choisi), de la longueur du texte, et de la présence de valeurs déjà connues dans la mémoire du thread.

Mitigation : mesurer sur votre propre charge avant de dimensionner le trafic de production. Garder les détecteurs sur GPU quand c'est possible pour les chemins à forte densité NER.

Couverture minimale des menaces

piighost traite l'exfiltration vers le LLM et son hébergeur. Il ne remplace pas le chiffrement au repos, le contrôle d'accès, ni les bonnes pratiques de journalisation du reste de votre système. Voir Sécurité pour le modèle de menaces complet.