Protéger un message avant l'envoi au modèle
En bref
- Avant d'envoyer un texte au modèle,
piighosty repère les valeurs sensibles et les remplace par des jetons comme<<PERSON:1>>. - Une même valeur reçoit un seul jeton dans tout le texte, même écrite avec une autre casse ou d'autres espaces.
- La réponse du modèle est ensuite restaurée. Chaque jeton redevient la vraie valeur.
- Une valeur que le détecteur ne voit pas part en clair, sauf si un contrôle final est activé. Ce contrôle bloque alors l'envoi.
- Les motifs ne vérifient pas les clés de contrôle (carte, IBAN). Une valeur mal recopiée reste masquée.
Besoins couverts, décrits dans Besoins par profil :
Les termes sont définis dans le glossaire. Pour une conversation en plusieurs messages, lisez ensuite Suivre une conversation et restaurer la réponse.
Pour le métier
piighost n'a pas d'écran. Ce que vous pouvez constater, c'est le texte reçu par le modèle et la réponse rendue à l'utilisateur. Pour essayer sur une phrase, l'équipe technique lance piighost anonymize "votre phrase".
Qui intervient
| Acteur | Rôle |
|---|---|
| L'utilisateur final | écrit son message en clair |
| L'application | reçoit le message et le fait passer par piighost avant d'appeler le modèle |
piighost | repère les valeurs, les remplace par des jetons, garde la correspondance |
| Le modèle | reçoit le texte protégé, et seulement lui |
Avant tout envoi, au moins un détecteur doit être configuré. piighost n'embarque aucun motif, donc les types protégés sont ceux des détecteurs et des groupes de motifs que la configuration charge.
Le trajet d'un message
Dans l'exemple suivi d'un bout à l'autre, l'utilisateur écrit « Écrivez à Jean Dupont, jean.dupont@exemple.fr ».
- Repérage. Un ou plusieurs détecteurs cherchent les valeurs par forme (e-mail, téléphone), par modèle d'IA (noms, lieux) ou par grand modèle de langage. Ici, « Jean Dupont » est repéré comme personne et « jean.dupont@exemple.fr » comme e-mail.
- Liste à masquer et liste à laisser en clair. Les valeurs à toujours masquer ou à ne jamais masquer, écrites dans la configuration (
deny_listetallow_list), sont appliquées. Voir Imposer une liste à masquer et une liste à laisser en clair. - Chevauchements. Quand deux repérages se recouvrent, un seul passage est gardé. Ici, rien ne se recouvre.
- Occurrences oubliées (étape facultative). Chaque valeur trouvée est recherchée ailleurs dans le texte.
- Regroupement. Les occurrences d'une même valeur et d'un même type forment un seul groupe.
- Remplacement. Chaque groupe reçoit un jeton. « Jean Dupont » devient
<<PERSON:1>>, l'adresse devient<<EMAIL:1>>. - Contrôle final (facultatif). Le texte protégé est relu pour y chercher un reste de valeur sensible.
Le modèle reçoit « Écrivez à <<PERSON:1>>, <<EMAIL:1>> ». piighost garde la correspondance entre chaque jeton et sa valeur, pour restaurer la réponse.
Comment vérifier : faites protéger la phrase d'exemple par l'équipe technique. La sortie ne doit contenir ni « Jean Dupont » ni l'adresse.
Règles à connaître
BR-MSG-01 . Quand une valeur est repérée, alors elle reçoit un jeton qui nomme son type et un numéro, compté par type dans l'ordre d'apparition. Par exemple, « Jean Dupont écrit à Marie Curie » devient « <<PERSON:1>> écrit à <<PERSON:2>> ». 2 emplacements
BR-MSG-02 . Quand une valeur revient sous une autre casse ou avec d'autres espaces, alors elle reçoit le même jeton. Par exemple, « Patrick a appelé. Rappelez patrick demain. » devient « <<PERSON:1>> a appelé. Rappelez <<PERSON:1>> demain. » 2 emplacements · 36 tests directs
BR-MSG-03 . Quand une valeur a plusieurs graphies, alors la restauration remet partout la première graphie rencontrée. Dans l'exemple précédent, la réponse restaurée affiche « Patrick » aux deux endroits. 2 emplacements
BR-MSG-04 . Quand un nom est collé à un autre par un trait d'union, alors il n'est pas reconnu comme le même mot. Par exemple, « Patrick » repéré ne masque pas « Jean-Patrick ». La raison est qu'un prénom court ne doit pas être relié à un prénom composé différent. 1 emplacement
BR-MSG-05 . Quand deux repérages se recouvrent, alors un seul passage est toujours gardé, et le plus sûr gagne (réglage par défaut). Le reste du plus long passage peut alors partir en clair. Un second réglage masque toute la zone couverte. 3 emplacements · 11 tests directs
Le tableau suivant compare les deux réglages :
| Réglage | « Contrat signé par Loni M. Wirth le 12 mars 2026. » devient |
|---|---|
| Le plus sûr gagne (par défaut) | Contrat signé par Loni M. <<PERSON:1>> le 12 mars 2026. |
| Union des passages | Contrat signé par <<PERSON:1>> le 12 mars 2026. |
Ici, un motif sûr à 100 % a trouvé « Wirth » et un modèle sûr à 70 % a trouvé « Loni M. Wirth ». À égalité parfaite de confiance et de position, le premier détecteur déclaré gagne. Avec l'union, la zone prend le type du repérage le plus sûr, et à confiance égale celui du plus long.
BR-MSG-06 . Quand une valeur est écrite avec une espace insécable ou fine, alors un motif écrit avec une espace normale la trouve quand même. Par exemple, « 06 12 34 56 78 » tapé dans un traitement de texte, avec des espaces insécables, devient <<PHONE:1>>. 2 emplacements · 11 tests directs
BR-MSG-07 . Quand un motif reconnaît la forme d'une carte ou d'un IBAN, alors la valeur est masquée sans vérifier sa clé de contrôle. La raison est qu'une valeur abîmée par une reconnaissance de caractères aurait une clé fausse, et que la rejeter la laisserait partir en clair. 1 emplacement · 11 tests directs
BR-MSG-08 . Quand un message contient une clé d'API, alors elle n'est masquée que si la configuration charge un groupe de motifs de secrets ou un modèle qui cherche les secrets. Le groupe piighost/logs du catalogue est un tel groupe. Par exemple, avec ce groupe, une clé OpenAI part sous la forme <<OPENAI_API_KEY:1>>. 2 emplacements · 12 tests directs
BR-MSG-09 . Quand l'utilisateur tape lui-même un texte qui a la forme d'un jeton, alors ce texte est neutralisé par un caractère invisible. Par exemple, « Claire écrit <<PERSON:2>> ici » ne pourra pas se faire passer pour un vrai jeton à la restauration. Sans cela, un jeton tapé à la main pourrait récupérer la valeur d'une autre personne. 2 emplacements · 41 tests directs
BR-MSG-10 . Quand le détecteur ne voit pas une valeur et qu'aucun contrôle final n'est activé, alors la valeur part en clair. Par exemple, si les e-mails ne sont pas reconnus, le texte devient « Écrivez à <<PERSON:1>>, jean.dupont@exemple.fr ». 1 emplacement
BR-MSG-11 . Quand le contrôle final trouve une valeur sensible dans le texte protégé, alors l'envoi est bloqué avec Anonymized text still contains PII: ['EMAIL']. Le message nomme les types restants, jamais les valeurs. 1 emplacement
BR-MSG-12 . Quand la recherche des occurrences oubliées est active, alors elle ignore la casse. Elle trouve « patrick » après « Patrick », au prix de faux positifs sur des mots courants. 1 emplacement · 16 tests directs
Ce que voit l'utilisateur final
Rien. Il écrit en clair et lit une réponse en clair. Seul le modèle voit les jetons. Si le contrôle final bloque un message, l'application reçoit une erreur. Le message affiché à l'utilisateur dépend alors de l'application.
Questions fréquentes
Une adresse e-mail est partie en clair. Le détecteur ne connaît pas ce type et aucun contrôle final n'est activé (BR-MSG-10 ). Faites ajouter le type au détecteur, ou activez un contrôle final qui le reconnaît.
Une partie d'un nom est partie en clair (« Loni M. »). Deux repérages se chevauchaient, et le plus sûr ne couvrait qu'une partie du nom (BR-MSG-05 ). Demandez le réglage « Union des passages ».
Le nom de l'entreprise est remplacé par <<PERSON:2>>. Le détecteur le prend pour une personne, et le modèle perd une information utile. Faites-le mettre dans la liste à laisser en clair, voir Imposer une liste à masquer et une liste à laisser en clair.
Un numéro de carte faux a été masqué. C'est voulu, parce qu'aucune clé de contrôle n'est vérifiée (BR-MSG-07 ).
Une clé d'API est partie en clair. Aucun groupe de motifs de secrets n'est chargé (BR-MSG-08 ). Faites ajouter le groupe piighost/logs à la configuration.
« Jean-Patrick » est resté en clair alors que « Patrick » est masqué. Le trait d'union lie les deux prénoms (BR-MSG-04 ). Il faut que le détecteur repère « Jean-Patrick » lui-même.
Le traitement s'arrête avec Anonymized text still contains PII. Le contrôle final a trouvé un reste (BR-MSG-11 ). Faites ajouter le type manquant au détecteur principal.
Pour les développeurs
Où vivent les règles
| Règle | Emplacement |
|---|---|
| Ordre des étapes | src/piighost/pipeline/base.py:352-393 (AnonymizationPipeline.anonymize), liste à masquer et liste à laisser en clair ligne 365 |
| BR-MSG-01 | components/placeholder/label_counter.py, défaut pipeline/base.py:172 |
| BR-MSG-02 | components/linker/exact.py:8-21, text/normalization.py:61-71 (value_key) |
| BR-MSG-03 | components/anonymizer/base.py:151 (deanonymize remplace par entity.text), models/entity.py |
| BR-MSG-04 | text/boundaries.py:38 (WORD_JOIN_CHARS) |
| BR-MSG-05 | components/overlap_resolver/confidence.py:10-21, merge.py:7-15 (_surest), overlap_resolver/base.py (by_confidence, _conflict_groups) |
| BR-MSG-06 | text/normalization.py:45-58, components/detector/regex.py:76-90 |
| BR-MSG-07 | components/detector/regex.py:10-30 |
| BR-MSG-08 | components/detector/regex.py:33-57 (from_catalog), config/models/detector.py (catalogs) |
| BR-MSG-09 | components/anonymizer/span.py:26-40, _neutralize ligne 79 |
| BR-MSG-10 , BR-MSG-11 | pipeline/base.py:313-341 (_guard) |
| BR-MSG-12 | components/expander/word_boundary.py:11-31 |
Quand seul le détecteur est fourni, les valeurs par défaut sont ExactEntityLinker, Anonymizer(LabelCounterPlaceholderFactory()) et ConfidenceOverlapResolver (pipeline/base.py:168-182). L'expansion, la résolution d'entités, la liste à masquer et la liste à laisser en clair (override) et le contrôle final (guard) sont désactivés.
import asyncio
from piighost.components.detector import ExactMatchDetector
from piighost.pipeline import AnonymizationPipeline
detector = ExactMatchDetector(
{"Jean Dupont": "PERSON", "jean.dupont@exemple.fr": "EMAIL"}
)
pipeline = AnonymizationPipeline(detector)
async def main() -> None:
result = await pipeline.anonymize("Écrivez à Jean Dupont, jean.dupont@exemple.fr")
print(result.text) # Écrivez à <<PERSON:1>>, <<EMAIL:1>>
asyncio.run(main())Modifier une étape
- Choisissez le port de l'étape (voir Ajouter ou remplacer un composant).
- Passez votre composant au constructeur,
AnonymizationPipeline(detector, overlap_resolver=MergeOverlapResolver()), ou en configuration[overlap_resolver] type = "merge". - Pour le contrôle final, passez
guard=DetectorGuardRail(un_détecteur).
Vérifier
uv run pytest tests/pipeline/test_pipeline.py tests/components tests/acceptance/test_dpo.pyPuis echo "Tél. 06 12 34 56 78" | uv run piighost anonymize --config <fichier> doit renvoyer un jeton à la place du numéro.
Pièges
- Le résolveur de chevauchements ne se désactive pas.
overlap_resolver=NoneinstalleConfidenceOverlapResolver.Anonymizer.renderlèveOverlappingSpansErrors'il reste un chevauchement. - L'expansion tourne après le résolveur. Elle saute toute occurrence qui touche un caractère déjà couvert, et cherche les valeurs les plus longues d'abord (
expander/base.py:30-75). - Le caractère de neutralisation reste dans le texte restauré. Un jeton tapé par l'utilisateur revient avec un U+200B invisible après son premier caractère. Un traitement en aval qui compare des chaînes exactes peut échouer.
Anonymizer(factory, escape_existing_tokens=False)désactive la neutralisation, au prix de BR-MSG-09 . RegexDetectorcompile sousre.ASCII.\dne prend que 0 à 9, et\ws'arrête au premier caractère accentué.- Un détecteur seul peut rendre des détections qui se chevauchent. Le port l'autorise. Ne testez pas la sortie d'un détecteur seul comme si elle était déjà passée par le résolveur de chevauchements.
- Un garde-fou à score (modération) ne localise rien. Les valeurs de la liste à laisser en clair ne peuvent pas en être exemptées (
pipeline/base.py:318-322). LLMDetectoréchoue fermé. Une sortie du modèle illisible, sans champentitiesou que le parseur rejette, lèveUnreadableOutputErroret le message est refusé (components/detector/llm.py:174-181,_unreadableen:203).fail_open=Truela lit comme zéro détection, et le message part alors sans protection. Voir DPO-9 dans Besoins par profil.
Tests
| Test | Couvre |
|---|---|
tests/pipeline/test_pipeline.py | Ordre des étapes, valeurs par défaut, contrôle final |
tests/components/overlap_resolver/ | Les deux résolveurs, l'égalité gagnée par le premier détecteur, l'union nommée d'après le plus large |
tests/components/expander/test_word_boundary.py | Recherche des occurrences, pas de chevauchement ajouté |
tests/components/anonymizer/test_span_anonymizer.py | Remplacement, neutralisation des jetons tapés, refus d'un chevauchement |
tests/text/ | Limites de mots, normalisation des espaces |
tests/components/detector/test_contract.py | Même sortie pour tous les détecteurs |
tests/acceptance/test_dpo.py | AT-DPO-1-2 (une clé d'API part en jeton), AT-DPO-2-2 (un groupe retiré laisse ses valeurs en clair) |