Introduction
S’il y a bien un sujet qu’un développeur ne peut plus ignorer aujourd’hui, c’est le RGPD. Que ce soit lors de la création d’environnements de test, de la génération de fixtures ou simplement dans le cadre d’une mission chez un client, nous sommes régulièrement confrontés à des données personnelles.
La théorie est simple : les données de production ne devraient jamais être utilisées sans contrôle dans un environnement de développement ou de recette.
La pratique est souvent bien différente.
Combien de fois avons-nous entendu :
« Tenez, voici l’export CSV de nos clients, regardez ce qu’il se passe. »
Et presque systématiquement, on y retrouve des informations sensibles : noms, adresses, emails, numéros de téléphone, voire parfois des données bien plus critiques.
Autre situation classique : télécharger une sauvegarde de production afin de reproduire un bug complexe ou disposer d’un snapshot réaliste de la base de données en environnement de développement. Bien que pratique, cette approche soulève immédiatement des questions de conformité et de protection des données personnelles.
La problématique est donc connue : comment conserver des données exploitables pour les développeurs tout en supprimant les informations permettant d’identifier les personnes concernées ?
C’est précisément sur ce point qu’intervient le nouveau modèle OpenAI Privacy Filter. Son objectif est de détecter et masquer automatiquement les données sensibles présentes dans un texte ou dans des données structurées, tout en pouvant être exécuté localement, sans dépendance à un service cloud externe.
Pour les équipes techniques soumises aux contraintes du RGPD, cela ouvre la voie à de nouveaux workflows d’anonymisation beaucoup plus simples à mettre en œuvre.
La légalité avant la technique
Il est impossible d’aborder le sujet du RGPD sans évoquer le rôle de la CNIL en France. Au-delà de son rôle de contrôle, la Commission Nationale de l’Informatique et des Libertés contribue depuis des années à sensibiliser les entreprises, les administrations et les particuliers aux enjeux liés à la protection des données personnelles.
À titre personnel, je suis particulièrement attaché à ces questions, aussi bien sur ce blog que dans les projets professionnels auxquels je participe. La protection des données ne devrait pas être perçue comme une contrainte administrative supplémentaire, mais comme un élément fondamental de la confiance numérique.
Pourtant, sur le terrain, le constat est souvent le même : de nombreux acteurs sous-estiment encore la valeur des données qu’ils manipulent quotidiennement. Il est donc nécessaire de faire preuve de pédagogie, d’expliquer les risques concrets et d’accompagner les équipes dans la compréhension des enjeux.
Les exemples ne manquent pas. Une simple base client peut contenir des informations permettant d’identifier précisément une personne : nom, prénom, adresse électronique, numéro de téléphone, adresse postale ou encore historique d’achats. Entre de mauvaises mains, ces informations peuvent être utilisées à des fins de phishing, d’usurpation d’identité ou d’ingénierie sociale.
C’est également pour cette raison que l’argument souvent entendu :
« Je n’ai rien à cacher, j’accepte tous les cookies. »
mérite d’être nuancé.
La question n’est pas de savoir si une personne a quelque chose à cacher. La véritable question est de comprendre qui collecte les données, dans quel objectif, pendant combien de temps elles sont conservées et quelles corrélations peuvent être établies à partir de celles-ci.
Une donnée isolée paraît souvent anodine. En revanche, lorsqu’elle est croisée avec des dizaines d’autres sources d’information, elle peut permettre de dresser un profil extrêmement précis d’un individu : habitudes de navigation, centres d’intérêt, comportements d’achat, localisation ou encore préférences personnelles.
Le RGPD ne vise donc pas uniquement à protéger des secrets. Son objectif est avant tout de redonner aux citoyens le contrôle sur leurs données personnelles et d’imposer aux organisations une responsabilité dans la manière dont elles les collectent, les stockent et les utilisent.
C’est précisément dans cette logique que les mécanismes d’anonymisation et de pseudonymisation prennent toute leur importance dans les projets techniques modernes.
LLM local : garder le contrôle sur la confidentialité des données
Lorsqu’on parle d’intelligence artificielle, le réflexe est souvent le même : ouvrir ChatGPT dans son navigateur et lui soumettre le problème à résoudre.
Mais dans le contexte du RGPD et du traitement de données personnelles, cette approche n’est pas toujours adaptée.
Ici, l’objectif est très différent. Nous ne cherchons pas à analyser ou exploiter les données de nos utilisateurs. Nous voulons simplement identifier les informations sensibles afin de les anonymiser ou les masquer, tout en conservant la structure et la cohérence des données.
En d’autres termes, nous souhaitons préserver le flux d’information sans jamais conserver les valeurs permettant d’identifier une personne.
C’est précisément ce qui rend l’approche locale particulièrement intéressante.
Grâce à Python et à l’écosystème open source de l’IA, il devient possible d’exécuter ce type de modèle directement sur sa machine, sans dépendre d’une API externe et sans envoyer la moindre donnée vers un service tiers.
Pour de nombreuses entreprises, cette approche constitue déjà un avantage considérable en matière de conformité, de souveraineté des données et de maîtrise des coûts.
Les puces Apple Silicon changent la donne
Depuis plusieurs années, les processeurs Apple Silicon ont considérablement démocratisé l’exécution locale des modèles d’intelligence artificielle.
Même lorsqu’un modèle n’est pas spécifiquement optimisé pour macOS, Apple fournit une couche d’accélération appelée Metal Performance Shaders (MPS) qui permet d’utiliser directement le GPU au lieu du CPU pour les calculs d’inférence.
Dans la pratique, les gains observés varient selon le modèle utilisé, sa taille et la quantité de mémoire disponible, mais l’accélération est généralement suffisante pour rendre l’utilisation locale confortable sur un Mac moderne.
L’expérience obtenue n’a plus grand-chose à voir avec celle des premières générations de modèles open source qui nécessitaient souvent des configurations très musclées ou des GPU dédiés.
Bien entendu, les performances dépendront directement du matériel utilisé. Un Mac équipé d’une puce M2 Pro n’offrira pas les mêmes temps de traitement qu’une machine plus récente dotée d’une puce M4 Pro ou M5 Pro. La quantité de mémoire unifiée joue également un rôle majeur dès que l’on manipule des modèles plus volumineux.
Mais pour un cas d’usage ciblé comme l’anonymisation de données personnelles, les machines Apple récentes sont aujourd’hui largement capables d’exécuter ce type de traitement localement, sans cloud, sans API et sans faire sortir les données de l’entreprise.
OpenAI Privacy Filter : un modèle spécialisé plutôt qu’un assistant généraliste
Lorsque l’on découvre le projet OpenAI Privacy Filter, la première surprise est qu’il ne s’agit pas d’un nouveau chatbot destiné à concurrencer les modèles conversationnels classiques.
Au contraire, OpenAI a fait le choix d’un modèle spécialisé dans une tâche bien précise : détecter et transformer les informations permettant d’identifier une personne.
L’approche est particulièrement intéressante d’un point de vue technique. Là où un LLM classique est entraîné pour répondre à une infinité de questions, Privacy Filter est focalisé sur un objectif unique : repérer les données sensibles et les remplacer par des marqueurs cohérents.
Par exemple, un texte contenant :
Jean Dupont habite au 12 rue des Lilas à Paris et peut être contacté au 06 XX XX XX XX.
pourra être transformé en :
[PERSON_1] habite à [ADDRESS_1] et peut être contacté au [PHONE_1].
Le contenu reste exploitable pour les développeurs, les analystes ou les équipes de test, mais les données personnelles ont disparu.
C’est un point essentiel. Dans de nombreux scénarios, l’objectif n’est pas de supprimer entièrement l’information mais de préserver sa structure. Une adresse email doit rester une adresse email, un numéro de téléphone doit rester identifiable comme tel et une personne mentionnée plusieurs fois dans le même document doit conserver une cohérence de remplacement.
Cette capacité permet de travailler sur des jeux de données réalistes sans exposer les informations réelles des utilisateurs.
Regex PHP vs OpenAI Privacy Filter : le test terrain
En tant que développeur, le premier réflexe est souvent assez naturel : utiliser des expressions régulières.
Après tout, PHP sait très bien le faire. Détecter un email, un numéro de téléphone ou une date avec une regex, c’est rapide, connu et facile à intégrer dans un formulaire ou un script de nettoyage.
Je me suis donc amusé à créer un formulaire très simple, avec une injection aléatoire de données personnelles, puis à comparer deux approches :
- une approche classique basée sur des regex ;
- une approche basée sur le modèle open source OpenAI Privacy Filter, exécuté en local.
Le résultat est assez parlant.
Sur l’exemple ci-dessus, la regex détecte correctement les éléments les plus évidents, comme l’adresse email ou la date. Mais elle passe à côté d’informations pourtant sensibles : le nom, l’adresse postale, le numéro de téléphone ou encore le nom de la société.
Le modèle OpenAI Privacy Filter, lui, détecte beaucoup plus d’entités. Il identifie le nom complet, l’email, le téléphone, les fragments d’adresse, la société et la date. Pour un cas d’usage RGPD, c’est évidemment beaucoup plus intéressant, car les données personnelles ne se limitent pas à un email ou à une date bien formatée.
L’autre point intéressant, c’est que le modèle annote le texte avant de le masquer. On peut donc comprendre ce qu’il a détecté, visualiser les entités sensibles, puis décider comment les remplacer dans le flux de traitement.
Ce qui m’a le plus surpris
En regardant les résultats, ce n’est pas tant la détection de l’email ou de la date qui impressionne. Une expression régulière bien écrite sait faire cela depuis des années.
Ce qui change réellement la donne, c’est la capacité du modèle à comprendre le contexte.
Le nom complet est identifié sans qu’aucune règle spécifique n’ait été écrite. L’adresse est reconnue comme une adresse alors qu’elle est composée de plusieurs éléments distincts. Le numéro de téléphone est détecté malgré un formatage typiquement français que beaucoup de regex simplifiées ne couvrent pas correctement.
Autrement dit, nous ne sommes plus dans une logique de recherche de motifs mais dans une logique de compréhension du texte.
C’est précisément ce qui rend l’approche intéressante pour les équipes qui manipulent des données réelles. Dans un export client, dans un CSV métier ou dans des documents bureautiques, les informations personnelles ne suivent presque jamais un format parfaitement normalisé.
Une regex fonctionne parfaitement lorsque le format est connu à l’avance.
Un modèle spécialisé fonctionne même lorsque le format n’a jamais été rencontré dans votre code.
Le coût caché des regex
Lorsque l’on développe une première version d’un système d’anonymisation, la solution basée sur des regex paraît souvent plus simple.
Puis arrivent les cas particuliers :
- les numéros internationaux ;
- les adresses incomplètes ;
- les sociétés ;
- les noms composés ;
- les données multilingues ;
- les fautes de frappe ;
- les formats métier spécifiques.
À chaque nouveau cas, une nouvelle règle apparaît.
Quelques mois plus tard, le projet contient plusieurs centaines de lignes de patterns difficiles à maintenir, dont personne ne comprend réellement toutes les implications.
Le coût n’est donc pas uniquement celui de l’implémentation initiale.
Le véritable coût est celui de la maintenance.
À l’inverse, OpenAI Privacy Filter déplace cette complexité dans le modèle lui-même. Le développeur ne maintient plus une collection de règles ; il maintient simplement un pipeline de traitement.
Un faux positif est parfois préférable à une fuite
Le cas de Northwind Labs illustre parfaitement cette philosophie.
Oui, le modèle sur-détecte.
Oui, il masque davantage d’informations que nécessaire.
Mais dans un contexte RGPD, le risque principal n’est pas de masquer une information de trop.
Le risque est de laisser passer une information sensible qui se retrouvera ensuite dans un environnement de développement, un outil d’analyse ou un jeu de données partagé.
Entre un faux positif et une fuite de données personnelles, le choix est généralement assez simple.
Une précision impressionnante pour une première version
Après les tests unitaires et les exemples simples, j’ai voulu voir comment le modèle se comportait sur un volume de données un peu plus conséquent.
Pour cela, j’ai généré plusieurs centaines d’enregistrements synthétiques à l’aide de Zenstruck Foundry et du système de fixtures Symfony. L’objectif n’était pas de construire un benchmark scientifique, mais plutôt de reproduire un contexte proche de ce que l’on rencontre lors du développement d’une application métier.
Au total, ce sont plus de 600 documents générés à partir de 200 profils synthétiques qui ont été analysés par OpenAI Privacy Filter.
Comme le montre la capture d’écran, les résultats sont particulièrement encourageants pour une première version publique du modèle.
Les métriques globales affichent :
- 90,1 % de précision ;
- 91,3 % de rappel ;
- 90,7 % de score F1.
Pour rappel :
- la précision mesure la proportion de détections correctes ;
- le rappel mesure la capacité du modèle à retrouver toutes les données sensibles présentes ;
- le F1 Score représente l’équilibre entre les deux.
Autrement dit, le modèle ne se contente pas de détecter beaucoup d’informations : il parvient également à limiter les erreurs de classification.
Certaines catégories atteignent pratiquement 100 %
Les résultats détaillés sont encore plus intéressants.
Comme les profils sont générés par Foundry et le bundle de fixtures, je connais à l’avance chaque entité injectée, son type et sa position dans le texte. Cette vérité terrain sert de référence : je compare les spans détectés par le modèle aux spans attendus, sur le type et sur la position au caractère près (--eval-mode typed, --span-metrics-space char). C’est volontairement strict — une bonne détection mal positionnée compte comme une erreur.
On observe notamment :
| Type de donnée | F1 Score |
|---|---|
| Téléphone | 100 % |
| Date | 100 % |
| Compte bancaire | 99,2 % |
| 98,3 % | |
| Personne | 85,4 % |
| URL | 86,4 % |
Les catégories les plus structurées, comme les numéros de téléphone, les emails ou les dates, obtiennent logiquement les meilleurs scores.
Les entités plus complexes, comme les personnes ou les adresses, restent plus difficiles à détecter avec une précision absolue. C’est d’ailleurs un problème connu dans le domaine du Named Entity Recognition (NER) depuis des années.
Malgré cela, les performances restent largement suffisantes pour envisager une intégration dans un pipeline d’anonymisation automatisé.
Le service Symfony pour appeler OpenAI Privacy Filter
Côté intégration, le code reste volontairement simple. L’idée n’est pas de reconstruire une couche d’abstraction complexe autour du modèle, mais simplement de permettre à Symfony d’appeler le binaire opf comme un outil système.
Le service repose sur le composant Process de Symfony. On lui transmet un texte, il l’écrit dans un fichier temporaire, appelle OpenAI Privacy Filter en ligne de commande, puis récupère le résultat JSON retourné par le modèle.
final class OpenAiPrivacyFilterRunner
{
public function __construct(
private readonly ?string $binaryPath = null,
private readonly int $timeoutSeconds = 1800,
private readonly ?string $device = null,
) {
}
private function resolveBinaryPath(): string
{
if ($this->binaryPath !== null) {
return $this->binaryPath;
}
$environmentPath = $_SERVER['OPF_BIN'] ?? $_ENV['OPF_BIN'] ?? getenv('OPF_BIN');
return is_string($environmentPath) && $environmentPath !== ''
? $environmentPath
: '/private/tmp/opf-venv/bin/opf';
}
public function isAvailable(): bool
{
$binaryPath = $this->resolveBinaryPath();
return is_file($binaryPath) && is_executable($binaryPath);
}
public function getBinaryPath(): string
{
return $this->resolveBinaryPath();
}
public function getDevice(): string
{
if ($this->device !== null) {
return $this->device;
}
$environmentDevice = $_SERVER['OPF_DEVICE'] ?? $_ENV['OPF_DEVICE'] ?? getenv('OPF_DEVICE');
return is_string($environmentDevice) && $environmentDevice !== ''
? $environmentDevice
: 'cpu';
}
public function redact(string $content): OpenAiPrivacyFilterResult
{
$binaryPath = $this->resolveBinaryPath();
if (!$this->isAvailable()) {
throw new \RuntimeException(sprintf('OpenAI Privacy Filter CLI not found at "%s".', $binaryPath));
}
$temporaryPath = tempnam(sys_get_temp_dir(), 'opf-input-');
if ($temporaryPath === false) {
throw new \RuntimeException('Unable to create a temporary input file for OpenAI Privacy Filter.');
}
chmod($temporaryPath, 0600);
if (file_put_contents($temporaryPath, $content) === false) {
@unlink($temporaryPath);
throw new \RuntimeException('Unable to write the temporary OpenAI Privacy Filter input file.');
}
try {
$process = new Process([
$binaryPath,
'--device',
$this->getDevice(),
'--output-mode',
'typed',
'--format',
'json',
'--no-print-color-coded-text',
'--text-file',
$temporaryPath,
]);
$process->setEnv($this->getProcessEnvironment());
$process->setTimeout($this->timeoutSeconds);
$process->run();
} finally {
@unlink($temporaryPath);
}
if (!$process->isSuccessful()) {
$errorOutput = trim($process->getErrorOutput() ?: $process->getOutput());
throw new \RuntimeException($errorOutput !== '' ? $errorOutput : 'Unable to run the OpenAI Privacy Filter CLI.');
}
/** @var array<string, mixed> $payload */
$payload = json_decode($process->getOutput(), true, 512, JSON_THROW_ON_ERROR);
$detectedSpans = is_array($payload['detected_spans'] ?? null) ? $payload['detected_spans'] : [];
$normalizedSpans = array_values(array_map(
static function (array $span): array {
return [
'label' => (string) ($span['label'] ?? 'unknown'),
'text' => (string) ($span['text'] ?? ''),
'start' => (int) ($span['start'] ?? 0),
'end' => (int) ($span['end'] ?? 0),
];
},
$detectedSpans
));
return new OpenAiPrivacyFilterResult(
(string) ($payload['redacted_text'] ?? ''),
$normalizedSpans,
$payload,
);
}
public function evaluate(string $datasetPath, string $outputDirectory): OpenAiPrivacyFilterEvaluationResult
{
$binaryPath = $this->resolveBinaryPath();
if (!$this->isAvailable()) {
throw new \RuntimeException(sprintf('OpenAI Privacy Filter CLI not found at "%s".', $binaryPath));
}
if (!is_file($datasetPath)) {
throw new \InvalidArgumentException(sprintf('Benchmark dataset not found at "%s".', $datasetPath));
}
if (!is_dir($outputDirectory) && !mkdir($outputDirectory, 0775, true) && !is_dir($outputDirectory)) {
throw new \RuntimeException(sprintf('Unable to create benchmark output directory "%s".', $outputDirectory));
}
$metricsPath = $outputDirectory . '/metrics.json';
$timingsPath = $outputDirectory . '/timings.json';
$predictionsPath = $outputDirectory . '/predictions.jsonl';
$process = new Process([
$binaryPath,
'eval',
$datasetPath,
'--device',
$this->getDevice(),
'--eval-mode',
'typed',
'--span-metrics-space',
'char',
'--per-class',
'--label-counts',
'--preprocess-workers',
'1',
'--prediction-write-workers',
'1',
'--metrics-out',
$metricsPath,
'--timings-out',
$timingsPath,
'--predictions-out',
$predictionsPath,
]);
$process->setEnv($this->getProcessEnvironment());
$process->setTimeout($this->timeoutSeconds);
$process->run();
if (!$process->isSuccessful()) {
$errorOutput = trim($process->getErrorOutput() ?: $process->getOutput());
throw new \RuntimeException($errorOutput !== '' ? $errorOutput : 'Unable to evaluate the OpenAI Privacy Filter model.');
}
/** @var array<string, mixed> $metrics */
$metrics = json_decode((string) file_get_contents($metricsPath), true, 512, JSON_THROW_ON_ERROR);
/** @var array<string, mixed> $timings */
$timings = json_decode((string) file_get_contents($timingsPath), true, 512, JSON_THROW_ON_ERROR);
return new OpenAiPrivacyFilterEvaluationResult($metrics, $timings, $process->getOutput());
}
/**
* @return array<string, string>
*/
private function getProcessEnvironment(): array
{
return $this->getDevice() === 'mps'
? ['OPF_MOE_TRITON' => '0']
: [];
}
}
Le point important ici est que PHP ne fait pas tourner le modèle directement. PHP orchestre simplement le traitement. Le vrai travail est délégué au runtime Python et au modèle exécuté localement.
C’est une approche assez saine : Symfony reste responsable du flux applicatif, tandis que Python reste dans son rôle naturel pour l’inférence IA.
Attention aux ressources machine
Même si l’intégration paraît légère côté code, il ne faut pas oublier ce qui tourne réellement derrière.
Sur la machine, on peut rapidement avoir en parallèle :
- le serveur PHP ;
- le process Python ;
- le modèle chargé en mémoire ;
- un navigateur web ;
- éventuellement une base de données ;
- et l’environnement de développement complet.
Il faut donc prévoir suffisamment de mémoire, d’espace disque et un environnement Python correctement installé.
Sur Mac, l’utilisation de mps permet de déléguer une partie du calcul au GPU Apple Silicon, ce qui rend l’expérience beaucoup plus confortable. Dans le service, cela se pilote simplement via une variable d’environnement :
OPF_DEVICE=mps
OPF_BIN=/private/tmp/opf-venv/bin/opf
Le service prévoit aussi un fallback en cpu, ce qui permet de garder un comportement explicite selon l’environnement.
Pourquoi passer par un fichier temporaire ?
Le choix du fichier temporaire peut sembler un peu old school, mais il a plusieurs avantages.
D’abord, cela évite les problèmes d’échappement lorsqu’on manipule des textes longs, des caractères spéciaux ou des contenus multi-lignes.
Ensuite, le fichier est créé avec des permissions restrictives :
chmod($temporaryPath, 0600);
Puis il est supprimé systématiquement dans un bloc finally.
C’est un détail important dans un article sur le RGPD : même le fichier d’entrée temporaire doit être traité comme une donnée sensible.
Une base simple pour aller vers le batch
Ce service ne sert pas uniquement à tester une phrase dans une interface.
Il peut devenir la base d’un worker dédié capable de traiter des fichiers en arrière-plan : exports CSV, documents générés par les fixtures, contenus JSON ou snapshots destinés aux environnements de développement.
C’est là que l’approche devient vraiment intéressante.
On peut imaginer un flux où l’application génère des données réalistes, les envoie au worker d’anonymisation, puis stocke uniquement la version nettoyée dans un environnement de test.
Le développeur conserve des données exploitables.
L’utilisateur conserve sa confidentialité.
Et l’entreprise évite de faire circuler des données personnelles là où elles n’ont rien à faire.
Conclusion
Au final, la bonne approche n’est probablement ni le tout regex, ni le tout LLM.
Les expressions régulières restent extrêmement efficaces pour des formats strictement définis : emails, IBAN, numéros de carte, identifiants techniques ou références métier. Elles sont rapides, déterministes et faciles à exécuter à grande échelle.
Les modèles comme OpenAI Privacy Filter interviennent là où les regex atteignent leurs limites : compréhension du contexte, détection des personnes, des adresses, des organisations ou des informations sensibles exprimées en langage naturel.
Pour moi, la vraie solution consiste à combiner les deux approches.
Les regex peuvent servir de premier filtre sur les données les plus structurées tandis que le modèle apporte une couche d’intelligence supplémentaire capable d’identifier ce qui aurait échappé aux règles classiques.
Comme souvent en ingénierie, il ne s’agit pas de remplacer une technologie par une autre, mais d’utiliser chaque outil là où il apporte le plus de valeur.
OpenAI Privacy Filter ne remplace pas les bonnes pratiques RGPD, les audits de sécurité ou les mécanismes d’anonymisation existants. En revanche, il apporte une nouvelle brique particulièrement intéressante pour automatiser des tâches qui étaient jusqu’à présent complexes, coûteuses ou difficiles à maintenir dans le temps.
C’est probablement ce qui m’a le plus convaincu durant mes tests : non pas la promesse de remplacer tout l’existant, mais la possibilité d’améliorer significativement les workflows déjà en place.