Outils

J’ai piégé une page HTML : watermarks-remover en retire 3 traces sur 4

15 août 2026 Mehdi 00:18

J’ai planté quatre signaux IA dans une page HTML de plan de sauvegarde. watermarks-remover en a trouvé trois (générateur ChatGPT, JSON-LD trainedAlgorithmicMedia, data-ai-model), les a retirés, et le fichier est passé de 586 à 405 octets. Le quatrième, un <meta name="ai-model" content="gpt-4o">, est resté. L’inspecteur ne l’a pas signalé.

La réécriture via un proxy local (openai/gpt-4o-mini, 2 candidats, humanize) a allongé le texte de 363 à 480 caractères et a ajouté une nuance sur les interruptions. Sans détecteur officiel avant/après, je ne peux pas dire que le filigrane statistique a disparu.

Unicode, métadonnées et biais statistique ne sont pas le même chantier

Le dépôt guillaumemeyer/watermarks-remover rassemble une skill pour agent et des scripts Python. Il vise des contenus que l’on possède, et il sépare trois familles souvent collées sous le mot « watermark » :

  1. les caractères Unicode invisibles, espaces exotiques et contrôles bidirectionnels ;
  2. les métadonnées de provenance (C2PA, EXIF, XMP, propriétés de documents) ;
  3. les filigranes statistiques collés au choix des mots par un modèle.

Le premier se traite de façon déterministe. Le deuxième dépend du format et des outils installés. Le troisième demande une réécriture, donc un appel à un modèle et une modification réelle du texte. Vérifier n’est pas réécrire, et le projet le dit clairement.

Élément Détail observé
Licence MIT
Runtime principal Python 3.10 ou plus récent
Dépendances du coeur Bibliothèque standard Python
Formats annoncés TXT, MD, HTML, PNG, JPEG, SVG, PDF, DOCX, ODT
Interface Scripts CLI et skill pour agent
Réécriture Ollama ou API compatible OpenAI
Outils optionnels exiftool, c2patool, reverse-SynthID, CtrlRegen
Version testée Commit 28eca2d

Pas d’interface graphique. Des scripts, à lancer à la main ou depuis un agent. Le coeur tient dans la bibliothèque standard. Les scripts refusent les URL non locales sans consentement, et ils refusent les binaires envoyés par erreur aux outils texte.

pytest passe, une fois le venv hors de /tmp

Le dépôt n’est pas un paquet Python classique. Il n’y a pas de pyproject.toml à installer avec pip. Les scripts principaux utilisent la bibliothèque standard. L’installation en tant que skill, c’est un lien symbolique vers skills/remove-ai-marks.

J’ai cloné le dépôt, créé un environnement Python et installé les dépendances de développement :

git clone https://github.com/guillaumemeyer/watermarks-remover
cd watermarks-remover
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest -q

Mon premier environnement virtuel placé sous /tmp n’était pas exécutable dans la sandbox. Je l’ai déplacé vers un volume exécutable, puis toute la suite de tests est passée. Ce détail ne vient pas du projet. Ça rappelle juste que l’installation est plus artisanale qu’un simple pip install.

Pour l’usage direct, on définit seulement le chemin des scripts :

SCRIPTS=skills/remove-ai-marks/scripts
python3 "$SCRIPTS/inspect_file.py" mon-fichier.html

Quatre signaux, trois trouvés, 586 octets deviennent 405

J’ai fabriqué une petite page HTML de plan de sauvegarde avec quatre signaux :

  • un générateur déclaré comme ChatGPT ;
  • un bloc JSON-LD avec trainedAlgorithmicMedia ;
  • un attribut data-ai-model sur le body ;
  • un caractère Unicode invisible dans une phrase.

inspect a trouvé les trois signaux de métadonnées et a renvoyé le code 1. Utile pour une CI, un cron ou un agent : non-zéro, il y a quelque chose à traiter.

python3 "$SCRIPTS/inspect_file.py" /tmp/scout-case.html
python3 "$SCRIPTS/clean_file.py" /tmp/scout-case.html \
  -o /tmp/scout-case.cleaned.html --json
python3 "$SCRIPTS/inspect_file.py" /tmp/scout-case.cleaned.html

clean a retiré le générateur, le JSON-LD, data-ai-model et le caractère Unicode invisible. Le fichier est passé de 586 à 405 octets. Une nouvelle inspection a donné AI metadata: False avec un code de sortie 0. Le titre visible et les deux paragraphes sont restés.

C’est le comportement que je veux pour un audit avant livraison : un rapport, un fichier de sortie, une vérif après. Pas besoin de clé API pour inspect ni pour clean. Une clé ou un modèle local n’entre en jeu que si on active la réécriture Layer B.

Le meta ai-model est resté

J’avais aussi ajouté <meta name="ai-model" content="gpt-4o">. Cette balise est restée dans le fichier nettoyé, et l’inspecteur ne l’a pas signalée. Les motifs connus couvrent notamment generator, openai, gemini, synthid, c2pa et provenance, pas ce nom générique.

Les trois marques documentées ont bien disparu. Un schéma maison ou un nouveau nom de champ peut passer. L’outil ne certifie pas qu’il n’y a plus de traces d’IA. Il retire des caractères et métadonnées connus, puis propose une réécriture au mieux pour les signaux statistiques. Il ne peut pas certifier le résultat face à tous les détecteurs propriétaires.

La réécriture a pris 363 caractères et en a rendu 480

J’ai testé la couche de réécriture avec le backend compatible OpenAI, un proxy local et le modèle openai/gpt-4o-mini. Deux candidats, mode humanize :

export WATERMARKS_REWRITE_BACKEND=openai-compatible
export WATERMARKS_REWRITE_BASE_URL=http://172.17.0.1:8790
export WATERMARKS_REWRITE_API_KEY=sk-proxy
export WATERMARKS_REWRITE_MODEL=openai/gpt-4o-mini
export WATERMARKS_REWRITE_ALLOW_REMOTE=1

python3 "$SCRIPTS/rewrite_text.py" /tmp/scout-rewrite.txt \
  -o /tmp/scout-rewritten.txt \
  --strength humanize --candidates 2 --json-stats

Le script a appelé le modèle, reçu 2 réponses, pris la plus divergente. Les scores internes étaient proches de 0,80. Le texte est passé de 363 à 480 caractères. Le sens sur la stratégie de sauvegarde a été conservé. La formulation s’est allongée et a ajouté une nuance non demandée sur les problèmes causés par une interruption.

On ne retire pas un signal statistique comme une balise. On remplace les choix de mots par ceux d’un autre modèle. Sans détecteur officiel avant et après, impossible d’affirmer que le filigrane visé a disparu. Pour un texte client, une relecture humaine reste obligatoire : le rewrite peut aplatir le style, changer la longueur, coller une nuance.

Le projet n’annonce pas une certification universelle. C2PA, un caractère invisible et un biais statistique restent trois problèmes distincts, même si le mot « watermark » sert pour les trois.

PDF sans exiftool, pixels non validés

Sur un PDF, le dépôt recommande exiftool pour un nettoyage fiable. Sans lui, la voie Python est explicitement dégradée et peut seulement tenter de retirer certains paquets XMP.

Les filigranes d’image invisibles dans les pixels passent par reverse-SynthID et CtrlRegen. Ça implique des dépôts tiers, de gros téléchargements et, pour CtrlRegen, un GPU fortement conseillé. Je n’ai pas validé cette chaîne pendant ce test.

Une CI est possible : les scripts produisent du JSON et utilisent des codes de sortie pour bloquer, avertir ou nettoyer. Il faut une politique claire pour les formats dégradés et pour les réécritures. Sinon on automatise un nettoyage partiel en croyant que c’est complet.

Lundi je branche inspect et clean, pas rewrite

Lundi, je câblerais inspect et clean déterministe avant une livraison. Léger, observable, JSON, exit non nul. Je ne l’allumerais pas comme preuve qu’un contenu est devenu « humain » ou qu’il trompera les détecteurs.

rewrite, je le laisse éteint par défaut. Je ne l’ouvre que si tuer un signal statistique possible vaut la perte de style, le coût du modèle et une revue éditoriale.

Projet testé : guillaumemeyer/watermarks-remover, commit 28eca2d.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *