
watermarks-remover v0.5.0
Eine Privacy-First-App, die KI-Wasserzeichen aus Inhalten entfernt, die dir gehören.
_ _ _ ____ ___ ____ ____ _ _ ____ ____ _ _ ____ ____ ____ _ _ ____ _ _ ____ ____
| | | |__| | |___ |__/ |\/| |__| |__/ |_/ [__ __ |__/ |___ |\/| | | | | |___ |__/
|_|_| | | | |___ | \ | | | | | \ | \_ ___] | \ |___ | | |__| \/ |___ | \
watermarks-remover
Agent-Skill + stdlib-Python-Service zum Entfernen herstellerübergreifender KI-Herkunftskennzeichen aus Text und Dateien — für Datenschutz und Hygiene bei Inhalten, die Ihnen gehören. Das Skill ist ein dünner Client: Es steuert die Maschinerie über HTTP, sodass der Agent-Host kein Python benötigt.
| Ebene | Ziel | Vorgehen |
|---|---|---|
| A | Unsichtbares Unicode, exotische Leerzeichen, Bidi, Tag-Zeichen | Deterministische Python-Skripte |
| B | Statistische (Token-Sampling-)Text-Wasserzeichen | Agent-Rewrite + optionaler rewrite_text.py-Hook |
| Dateien | C2PA / EXIF / XMP / Dokumenteigenschaften | PNG, JPEG, WebP, AVIF, HEIC, BMP, GIF, TIFF, SVG, PDF, DOCX, XLSX, PPTX, EPUB, ODT, HTML, Markdown, MP4/MOV/M4A/M4V, WAV, MP3, FLAC |
Hersteller / Ökosysteme (auf Klassenebene): Claude, Gemini / SynthID-Text, OpenAI-Herkunftsoberflächen, Open-LLM-Markierungen im Kirchenbauer-Stil (Green-List) sowie Keyed-Gumbel- / EXP-Markierungen (Aaronson).
Neueste Version: v0.7.0
Skill-Pfad: skills/remove-ai-marks/
Service-Pfad: service/
(Migration: früher remove-claude-marks; Slash-Alias /remove-claude-marks weiterhin dokumentiert)
Installation (Agent-Skill)
Das Skill enthält keinen Code — es ruft den Service über HTTP auf. Installieren Sie das Skill (nur Markdown) und starten Sie den Service, legen Sie dann WATERMARKS_SERVICE_URL fest, falls dieser nicht http://127.0.0.1:8765 lautet.
In Claude Code ist der schnellste Weg der mitgelieferte Plugin-Marketplace — kein Klonen, und er aktualisiert sich direkt. Überall sonst deckt ein Installer jeden unterstützten Host ab (Python 3.10+ stdlib, keine Abhängigkeiten):```bash python3 install_skill.py --skill remove-ai-marks --target claude-code
| Host | Ziel | Landet in |
| --- | --- | --- |
| Claude Code (persönlich) | `--target claude-code` | `~/.claude/skills/<skill>` (beachtet `CLAUDE_CONFIG_DIR`) |
| Claude Code (Projekt) | `--target claude-project --project-dir PATH` | `PATH/.claude/skills/<skill>` |
| Cowork, claude.ai, Cloud-Sessions, Routinen | `--target cowork` | `dist/<skill>.zip` zum Hochladen unter **Customize → Skills** |
| Cursor | `--target cursor` (Standard) | `~/.cursor/skills/<skill>` |
Mitgelieferte Skills: `remove-ai-marks` (vollständig, servicegestützt) und
`clean-user-facing-text` (nur Text, eigenständig). `--list` gibt sie aus.
Bestehende Installationen bleiben erhalten, sofern du nicht `--force` übergibst; der Austausch wird
zuerst vorbereitet und die vorherige Installation als eindeutig benanntes Backup aufbewahrt.
`--link` verlinkt diesen Checkout per Symlink, anstatt zu kopieren, sodass Änderungen live
übernommen werden. Unter Windows verwende `py install_skill.py ...`; der Wrapper `install-skill.sh`
ist für macOS/Linux-Shells vorgesehen.
Bevor etwas geschrieben wird, validiert der Installer den Skill gegen die
[Agent Skills](https://agentskills.io)-Packaging-Regeln, die claude.ai-Uploads
und die Skills API durchsetzen: nur spezifikationskonformes Frontmatter (`name`, `description`,
`license`, `compatibility`, `metadata`, `allowed-tools`), ein kleingeschriebener, mit Bindestrichen
verbundener `name` von höchstens 64 Zeichen, der mit dem Verzeichnis übereinstimmt, eine nicht leere
`description` von höchstens 1024 Zeichen. Das Cowork-Bundle muss zusätzlich
das Upload-Limit von 30 MB einhalten, was der Packager erzwingt.
### Automatische Bereinigung per Hook (deterministisch)
Ein Skill ist eine Anweisung: Das Modell entscheidet, ob es ihn aufruft, und das
Modell ist die Instanz, die die Markierungen erzeugt. Ein **Hook** wird von der Harness
bei jedem passenden Tool-Aufruf ausgeführt, ohne dass Kooperation erforderlich ist. Das macht den Hook zur
deterministischen Hälfte dieses Workflows.
Das Plugin registriert einen `PostToolUse`-Hook auf `Write|Edit|MultiEdit|NotebookEdit`,
der [`service/scripts/hook_written_file.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/hook_written_file.py)
gegen die Datei ausführt, die der Agent gerade geschrieben hat. Zwei Modi, entsprechend der Pre-Commit-
Konvention von standardmäßiger Prüfung:
| Modus | Verhalten |
| --- | --- |
| `check` (Standard) | Meldet Herkunftsmarkierungen, lässt die Datei unverändert. Befunde gehen an das Modell (Exit 2), damit es deren Bereinigung anbieten kann. |
| `clean` | Entfernt die Markierungen direkt, und teilt dem Modell anschließend mit, dass sich die Datei auf der Festplatte geändert hat. |
Lege den Modus in den Einstellungen des Plugins fest (**Hook mode** in `/plugin manage`,
vom Hook als `CLAUDE_PLUGIN_OPTION_HOOK_MODE` gelesen), oder mit
`WATERMARKS_HOOK_MODE=clean` in der Umgebung. Der Hook-Befehl interpoliert bewusst
**nicht** `${user_config.hook_mode}`: Claude Code weigert sich, einen Hook auszuführen, der auf eine Option verweist, die der Benutzer nie über `/plugin manage` geöffnet hat, um
sie festzulegen — ein deklarierter `default` erfüllt dies nicht —, sodass eine Interpolation bedeuten würde,
dass der Hook bei einer Neuinstallation stillschweigend nie ausgeführt wird. Die Erkennung verwendet `audit_lib`s
`scan_file` / `is_actionable` wieder, sodass der Hook, das Pre-Commit-Gate und der CI-
SARIF-Export darin übereinstimmen, was als actionable gilt; die Bereinigung ruft
`clean_file.py` auf, sodass keine Bereinigungslogik dupliziert wird. Der `clean`-Modus schreibt in eine
benachbarte temporäre Datei und tauscht nur bei einem echten Unterschied aus, sodass Dateien, die
bereits sauber waren, ihre mtime behalten und keine Datei-Watcher erneut auslösen.
Ohne das Plugin binde es selbst in `~/.claude/settings.json` (oder eine Projekt-
`.claude/settings.json`) ein:```json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
"hooks": [
{
"type": "command",
"command": "python3",
"args": ["/path/to/watermarks-remover/service/scripts/hook_written_file.py",
"--mode", "check"],
"timeout": 30
}
]
}
]
}
}
Unter Windows ersetzen Sie python3 durch py.
Was ein Hook nicht kann. Kein Hook kann die Chat-Nachricht des Assistenten umschreiben, bevor Sie sie lesen. Der Stop-Hook von Claude Code empfängt last_assistant_message schreibgeschützt, und es gibt keinen Vor-Versand-Filter für endgültige Antworten — dieselbe Einschränkung, die dieses Projekt bereits für Cursor-Regeln dokumentiert. Die deterministische Garantie deckt also Dateien ab, die der Agent schreibt, plus das Pre-Commit-Gate für alles, was auf dem Weg in Git ist. Text, der nur im Chat-Transkript existiert, hängt weiterhin vom Skill-Workflow ab, der auf Modell-Anweisungen basiert und daher Best-Effort ist.
Claude Code Plugin (Marketplace)
Das Repository ist auch ein Claude Code Plugin und ein Single-Plugin-Marketplace (.claude-plugin/), sodass beide Skills mit zwei Befehlen installiert und aktualisiert werden können, ohne Klonen oder Skript:```
/plugin marketplace add guillaumemeyer/watermarks-remover
/plugin install watermarks-remover@watermarks-remover
Die Skills werden dann mit Namespace geladen: `/watermarks-remover:remove-ai-marks` und
`/watermarks-remover:clean-user-facing-text` (das bloße `/remove-ai-marks` funktioniert ebenfalls,
wenn nichts anderes den Namen beansprucht). `/plugin marketplace update
watermarks-remover` zieht spätere Versionen. Dasselbe funktioniert über die CLI mit
`claude plugin marketplace add …` / `claude plugin install …` und aus einem lokalen
Checkout, indem ein Pfad anstelle von `owner/repo` übergeben wird.
Maintainer: `make plugin-validate` führt `claude plugin validate . --strict`
gegen beide Manifeste aus; `tests/test_plugin_manifest.py` deckt dieselben Dateien
ab, ohne dass die CLI benötigt wird.
### Claude Code```bash
# Personal — available in all your projects
python3 install_skill.py --skill remove-ai-marks --target claude-code
# or: make install-claude-code-skill
# Project — commit .claude/skills/ to share it with the repo
python3 install_skill.py --skill remove-ai-marks --target claude-project \
--project-dir /path/to/project
# or: make install-claude-project-skill PROJECT=/path/to/project
Claude Code erkennt persönliche und projektbezogene Skills ohne Neustart; /skills
listet auf, was geladen wurde. Aufrufen mit /remove-ai-marks oder darum bitten, „AI
Wasserzeichen / C2PA / Claude-Markierungen / Text der SynthID-Klasse zu entfernen“. Eine Projektinstallation ist
auch das, was Cloud-Sitzungen
lesen, da sie das Repository klonen und dessen .claude/skills/ laden.
Cowork (und claude.ai, Cloud-Sitzungen, Routinen)
Cowork-Sitzungen lesen nicht ~/.claude/skills auf deinem Rechner — sie laden
die für dein claude.ai-Konto aktivierten Skills, synchronisiert beim Start der Sitzung.
Installiere sie also dort, indem du ein Bundle hochlädst:```bash
python3 install_skill.py --skill remove-ai-marks --target cowork
writes dist/remove-ai-marks.zip (make package-cowork-skill)
Dann öffne in der Claude Desktop App **Customize → Skills → Add** und lade
die Zip-Datei hoch (dieselben Skill-Einstellungen auf claude.ai funktionieren ebenfalls). Das Bundle ist
reproduzierbar und enthält ein einzelnes Verzeichnis `remove-ai-marks/` auf oberster Ebene mit
`SKILL.md` in dessen Wurzel, was dem Layout entspricht, das der Upload erwartet.
Die Erreichbarkeit des Dienstes ist hier wichtiger als bei einer lokalen Installation: Der Skill ist ein
schlanker HTTP-Client, daher muss die Sitzung `WATERMARKS_SERVICE_URL` erreichen können.
Cowork-Sitzungen, die lokal auf deinem Rechner laufen, erreichen ein lokales `make serve`;
Cloud-Sitzungen und -Routinen laufen remote und benötigen eine von dort erreichbare Service-URL
(und einen darauf gesetzten `WATERMARKS_SERVER_API_KEY`). Wenn du einen Skill ohne
Dienst möchtest, lade stattdessen `clean-user-facing-text` hoch — er ist text-only und
bringt seine eigenen Skripte mit:```bash
python3 install_skill.py --skill clean-user-facing-text --target cowork
Grok```bash
Grok Build / project-local
mkdir -p .grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" .grok/skills/remove-ai-marks
User-global Grok
mkdir -p ~/.grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" ~/.grok/skills/remove-ai-marks
### Optionaler reiner Text-Skill
[`skills/clean-user-facing-text/`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/clean-user-facing-text) ist ein
eigenständiger Skill für autorisierte Manuskripte, Dokumentation und Web-
Texte. Er schließt Bild-, C2PA-, Service- und externe Modell-Tooling aus und führt
seine eigenen mitgelieferten Layer-A-Skripte aus, anstatt den Service aufzurufen.```bash
python3 install_skill.py --skill clean-user-facing-text --target claude-code
python3 install_skill.py --skill clean-user-facing-text --target cursor
Die Skill-Aufrufung wird vom Modell ausgewählt. Projekte, die diesen
Workflow in Cursor explizit übernehmen, können auch die optionale Regel kopieren:```bash
mkdir -p /path/to/project/.cursor/rules
cp integrations/cursor/clean-user-facing-text.mdc
/path/to/project/.cursor/rules/clean-user-facing-text.mdc
Für alle Projekte sollten Sie stattdessen dieselbe Anweisung in den **User Rules** von Cursor hinterlegen.
Regeln verbessern die Konsistenz, bleiben jedoch Modellanweisungen; Cursor bietet
keinen deterministischen Filter vor dem Versand für endgültige Chat-Antworten.
### Den Dienst starten
Der schnellste Weg ist ein lokaler HTTP-Server (nur Python 3.10+ stdlib — keine Abhängigkeiten, kein Docker):```bash
make serve # http://127.0.0.1:8765
# or directly:
python3 service/scripts/server.py --host 127.0.0.1 --port 8765
Windows (ohne Docker)
Siehe docs/windows-autostart.md für das automatische Starten des Dienstes bei der Windows-Anmeldung ohne Docker.
Für die gesamte Infrastruktur (Core + optionales Harness/Heavy-Backends) siehe Docker / compose unten.
Optionale Systemwerkzeuge (werden automatisch verwendet, wenn vorhanden — im Core-Docker-Image vorinstalliert):
| Werkzeug | Rolle |
|---|---|
c2patool | C2PA-Manifeste inspizieren |
exiftool | Entfernung residualer Metadaten (insb. PDF) |
qpdf | Struktureller PDF-Neuaufbau — erforderlich für eine echte PDF-Bereinigung (siehe unten) |
Core-Skripte benötigen nur die Python 3.10+-Standardbibliothek. Layer-B-Modellaufrufe sind optional.
Schnellverwendung (Skripte)```bash
SCRIPTS=service/scripts
Unified inspect / clean
python3 "$SCRIPTS/inspect_file.py" draft.md python3 "$SCRIPTS/clean_file.py" draft.md -o draft.cleaned.md python3 "$SCRIPTS/clean_file.py" photo.png -o photo.cleaned.png python3 "$SCRIPTS/clean_file.py" notes.docx -o notes.cleaned.docx
Text Layer A
python3 "$SCRIPTS/inspect_text.py" draft.md python3 "$SCRIPTS/clean_text.py" draft.md -o draft.cleaned.md --stats
Layer B rewrite hook (default: print prompt only — no model required)
python3 "$SCRIPTS/rewrite_text.py" draft.md --backend print-prompt --tactic paraphrase
Optional local Ollama (loopback only by default — remote endpoints require
WATERMARKS_REWRITE_ALLOW_REMOTE=1 or --allow-remote):
WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2 \
python3 "$SCRIPTS/rewrite_text.py" draft.md -o draft.rewritten.md
API keys are read from WATERMARKS_REWRITE_API_KEY only (never argv).
Images
python3 "$SCRIPTS/inspect_image.py" shot.png python3 "$SCRIPTS/clean_image.py" shot.png -o shot.cleaned.png
### Textwerkzeuge verweigern binäre Eingabe
`inspect_text.py`, `clean_text.py` und `rewrite_text.py` arbeiten mit Text. Auf eine
`.docx`-, `.pdf`- oder Bilddatei gerichtet, dekodierten sie früher die komprimierten Bytes und meldeten
welche Codepunkte auch immer herauskamen — Rauschen, das der Kompression folgt, nicht dem
Inhalt — und `clean_text.py` schrieb diese verstümmelten Bytes dann zurück und zerstörte die
Datei. Sie verweigern nun binäre Eingabe und nennen das Werkzeug, das sie verarbeitet:```bash
python3 "$SCRIPTS/inspect_text.py" report.docx
# refusing to treat report.docx as text: it looks like a ZIP container (DOCX, ODT, …).
# Use inspect_file.py / clean_file.py, which route by format,
# or pass --force-text to scan the raw bytes anyway.
Die Erkennung erfolgt über Magic Number plus ein Steuerbyte-Verhältnis, sodass Text in anderen Kodierungen als UTF-8 weiterhin funktioniert. --force-text überschreibt dies überall.
Unbekannte Formate werden niemals automatisch bereinigt
classify() kennzeichnet Bytes, die keinem unterstützten Text-, Bild- oder Containerformat entsprechen, als unknown — es fällt nicht mehr auf „Text" zurück. Im Auto-Modus lehnt clean_file.py solche Dateien ab (Exit 2, keine Ausgabe geschrieben), anstatt sie als UTF-8 zu dekodieren und verstümmelte Bytes zurückzuschreiben; --as text oder --force-text sind die expliziten Opt-ins. inspect_file.py meldet die Datei als unknown (Exit 0), und der HTTP-Dienst antwortet auf /inspect mit kind: "unknown", lehnt jedoch /clean von unbekannten Formaten ab (400 — senden Sie einen Dateinamen mit bekannter Erweiterung, z. B. notes.txt).
HTTP-Dienst
Dieselbe Maschinerie läuft als stdlib-HTTP-Dienst (service/scripts/server.py) — die Schnittstelle, die der Skill verwendet und über die jede Web-App ohne Vendoring integrieren kann:
| Methode | Pfad | Body | Rückgabe |
|---|---|---|---|
| GET | /health | — | {"ok": true, "version": ...} |
| GET | /capabilities | — | optionale Tools / Backends nutzbar (jedes Tool wird versionsgeprüft, nicht nur auf PATH gefunden) |
| GET | /openapi.json | — | dynamisch generierte OpenAPI 3.0.3-Spezifikation |
| POST | /inspect | {"file": "<base64>", "name": "notes.md"} | {"ok", "kind", "suspicious", "report"} |
| POST | /detect | {"file": "<base64>", "name": "notes.txt"} | {"ok", "kind", "detections": [...]} |
| POST | /clean | {"file": "<base64>", "name": "notes.md", "options": {...}} | {"ok", "kind", "cleaned": "<base64>", "report"} |
| POST | /watermark | {"text": "...", "keys": [118, 504, ...], "options": {...}} oder {"file": "<base64>", ...} | {"ok", "kind", "watermarked_text", "report": {"scheme_used", ...}} |
| POST | /inspect/batch | {"files": [{"file": "<base64>", "name": "notes.md"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "suspicious", "report"}, ...]} |
| POST | /detect/batch | {"files": [{"file": "<base64>", "name": "notes.txt"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "detections", "report"}, ...]} |
| POST | /clean/batch | {"files": [{"file": "<base64>", "name": "notes.md", "options": {...}}, ...]} | {"ok", "results": [{"name", "ok", "kind", "cleaned", "report"}, ...]} |
| POST | /watermark/batch | {"files": [{"text": "...", "keys": [...]}, {"file": "<base64>"}, ...]} | {"ok", "results": [{"name", "ok", "kind", "watermarked_text", "report": {"scheme_used", ...}}, ...]} |
Batch-Endpunkte durchlaufen dieselbe Pipeline pro Datei wie /inspect, /detect, /clean und /watermark, begrenzt auf WATERMARKS_MAX_BATCH_FILES Dateien pro Anfrage (Standard 50). Ein fehlerhafter Eintrag (ungültiges Base64, unbekannte Option, nicht erkanntes Format) erscheint als "ok": false dieses Eintrags mit einem "error"-String — er bricht niemals den Rest des Batches ab.```bash
WM="http://127.0.0.1:8765"
curl -s "$WM/health" # {"ok": true, "version": "..."}
curl -s "$WM/openapi.json" # machine-readable OpenAPI 3.0.3 contract
curl -s -X POST "$WM/clean" -H 'Content-Type: application/json'
-d "{"file": "$(base64 < notes.md | tr -d '\n')", "name": "notes.md"}"
Der Dienst leitet anhand der Dateiendung und dann anhand der Magic Bytes weiter, sodass Text / Bild / Container automatisch erkannt werden. Setzen Sie `WATERMARKS_SERVER_API_KEY`, um `Authorization: Bearer <key>` bei jeder Anfrage zu verlangen. Standardmäßig nur an Loopback gebunden (`--host` zum Überschreiben); gedacht für ein vertrauenswürdiges Netzwerk.
### Wasserzeichen-Erkennung (`/detect` und `detect_before` / `detect_after`)
Die Erkennung ist ein separater Schritt vom Bereinigen — der Dienst ruft niemals
Anbieter-APIs auf, es sei denn, Sie fordern es an:
- **`POST /detect`** führt die konfigurierten Wasserzeichen-Detektoren auf einer Datei aus.
Text → Anbieter-Detektoren + Stylometrie; Bild → SynthID-Pixel-Score.
- **`/inspect`** akzeptiert ein Opt-in-Flag `"detect": true`, das
Detektor-Ergebnisse an den Textbericht anhängt (und `suspicious` umschlagen lassen kann).
- **`/clean`** akzeptiert die Optionen `"detect_before"` / `"detect_after"`, um
die Eingabe und die bereinigte Ausgabe zu bewerten, sodass Sie messen können, was eine Bereinigung
tatsächlich verändert hat.
- **`/clean`** führt die Layer-B-Textumschreibung nach Layer A **standardmäßig** aus (sie
ist ein erforderlicher Schritt für Text). Eine **`"strategy"`**-Option (eine geordnete
`tactic@intensity`-Liste, z. B. `"[email protected],[email protected]"`) überschreibt den
Standard aus der Strategie-Konfigurationsdatei (siehe unten). Wenn das Rewrite-
Backend/Modell für einen Schritt nicht konfiguriert ist, gibt `/clean` einen 400 zurück.
Text-Detektoren (siehe `/capabilities` → `text_detectors`):
Text-Detektoren (siehe `/capabilities` → `text_detectors`):
| Detektor | Aktiviert durch | Hinweise |
| --- | --- | --- |
| `markllm` | `MARKLLM_DIR` (Host-Checkout) | Research-Harness (KGW / SynthID-Schemata), nur gleiche Konfiguration — kein Anbieter-Orakel. |
| `gumbel` | `WATERMARKS_GUMBEL_KEY` | Modellfreies Same-Key-Replay des keyed-Gumbel-(Aaronson-EXP)-Schemas (siehe `detect_gumbel.py`), nur stdlib — selbst gehostete Engines wie arbi-serve; nur Same-Key, kein Anbieter-Orakel. |
| `claude-text` | — (Platzhalter) | Anthropic hat eine Wasserzeichen-Erkennungs-API angekündigt; diese Schnittstelle wird aktiv, sobald sie verfügbar ist. |
Bildbewertung: Wenn `WATERMARKS_SYNTHID_SCORER_URL` gesetzt ist, bewertet der Dienst
Bilder über den `wr-synthid-score`-Sidecar (heavy-Profil); mit einem
lokalen `REVERSE_SYNTHID_DIR` verwendet er den Checkout direkt. Die Erkennung ist
fail-soft: nicht konfigurierte, zeitlich überschrittene oder fehlerhafte Detektoren melden
`{"available": false, "error": ...}` und blockieren niemals die Bereinigung.
### Wasserzeichen-Erzeugung (`/watermark` und `/watermark/batch`)
Erzeugt wasserzeichenbehafteten Text für Benchmark-Evaluierung und Round-Trip-Tests.
Wenn `WATERMARKS_SYNTHID_TEXT_URL` gesetzt ist, delegiert der Dienst die Erzeugung an den
`wr-synthid-text`-Sidecar (harness-Profil); mit einem lokalen `MARKLLM_DIR` verwendet er den
Checkout direkt. Wie bei der Erkennung ist die Erzeugung fail-soft: ein nicht konfigurierter Generator
meldet `{"ok": false, "error": ...}`.
## Docker / compose
Veröffentlichte Images (GHCR):
| Image-Tag | Inhalt | Veröffentlicht? |
| --- | --- | --- |
| `ghcr.io/guillaumemeyer/watermarks-remover:<tag>` / `:latest` | Kern-HTTP-Dienst + alle Cleaner + exiftool / qpdf / c2patool | Ja |
| `…:markllm-<tag>` / `:markllm-latest` | MarkLLM-Text-Wasserzeichen-Harness (Apache-2.0-Upstream) | Ja |
| `…:markdiffusion-<tag>` / `:markdiffusion-latest` | MarkDiffusion-Bild-Harness (Apache-2.0-Upstream) | Ja |
| `watermarks-remover-ctrlregen:local` | CtrlRegen-Pixel-Entfernung — **niemals veröffentlicht** (`noai-watermark` liefert keine LICENSE mit) | Nur lokaler Build |
| `watermarks-remover-synthid-scorer:local` | reverse-SynthID-Scorer — **niemals veröffentlicht** (nicht-kommerzielle Research-Lizenz) | Nur lokaler Build (CLI-Scorer + optionaler `wr-synthid-score`-HTTP-Sidecar unter dem `heavy`-Profil) |
Kern-Dienst bauen und ausführen:```bash
make docker-core-build
docker run --rm -p 127.0.0.1:8765:8765 --read-only --tmpfs /tmp watermarks-remover
# any CLI stays runnable by overriding the command:
docker run --rm -v "$(pwd):/data" watermarks-remover \
/app/scripts/clean_file.py /data/notes.md -o /data/notes.cleaned.md
Gesamte Infrastruktur-Inbetriebnahme:```bash docker compose up -d # core HTTP service only docker compose --profile harness up -d # + markllm / markdiffusion / wr-synthid-text sidecar docker compose --profile heavy up -d # + ctrlregen / synthid (local builds) docker compose --profile harness --profile heavy up -d # all services
Der Compose-Stack mappt den Core-Service auf `127.0.0.1:8765`. Persistente Services laufen als Hintergrund-Daemons (`wr-core` und der `wr-synthid-text`-Sidecar unter dem Harness-Profil). Die übrigen Harness-/Heavy-Services sind One-Shot-CLIs — rufe sie mit `docker compose run --rm <service> …` auf, wenn du Verifikation oder Pixel-Arbeit benötigst.
Validiere den laufenden Stack (nur Exit-Code, keine Ausgabe bei Erfolg):```bash
make compose-check # or: ./compose-check.sh
Prüft wr-core über GET /health und führt jeden Harness-/Heavy-Service mit --help aus, wobei Exit 0 erforderlich ist.
Konfiguration (Umgebungsvariablen für docker compose)
Textbereinigung erfordert Layer-B-Konfiguration — der Layer-B-Rewrite ist ein
erforderlicher Schritt für POST /clean bei Text, daher benötigt der Core-Service
das eingerichtete Rewrite-Backend, andernfalls gibt die Textbereinigung HTTP 400 zurück.
Die Bereinigung von Bild-/Container-Metadaten funktioniert ohne weitere Konfiguration. Für Text müssen Sie die
Layer-B-Strategie-Abhängigkeiten konfigurieren: transformers + roberta-large (für den Standard-mlm-Schritt) und
die WATERMARKS_REWRITE_*-LLM-Konfiguration (für den paraphrase-Schritt):```bash
echo "Hello\u200bWorld\u00ad!" > /tmp/sample.txt
curl -s -X POST http://127.0.0.1:8765/clean -H 'Content-Type: application/json'
-d "{"file": "$(base64 < /tmp/sample.txt | tr -d '\n')", "name": "sample.txt"}"
Sprachen, deren Typografie auf einem geschützten Leerzeichen beruht (Französisch `« … »`, das Leerzeichen vor `; : ! ?`), sollten `"options": {"normalize_spaces": false}` übergeben, das HTTP-Äquivalent zu `clean_text.py --no-normalize-spaces`. Unsichtbare Trägerzeichen werden weiterhin entfernt; nur die Leerzeichen-Umschreibung wird übersprungen.
Alles andere ist optional und befindet sich in einer `.env`-Datei im Repository-Stammverzeichnis. `docker compose` **lädt `.env` automatisch** und interpoliert daraus die `${VAR}`-Referenzen in `compose.yaml` (Shell-Exporte haben Vorrang vor `.env`, wenn beide gesetzt sind).```bash
cp .env.example .env # then edit
docker compose up -d # picks up .env automatically
.env ist gitignored (Deny-by-Default) — committe sie niemals. Für hostseitige CLI-Läufe (rewrite_text.py, der Skill) exportiere dieselbe Datei in die Umgebung:```bash
set -a; . ./.env; set +a; python3 service/scripts/rewrite_text.py /tmp/x.txt -o /tmp/x.rewritten.txt
| Var | Erreicht | Zweck |
| --- | --- | --- |
| `WATERMARKS_SERVER_API_KEY` | `wr-core` (über Compose `environment`) | Erfordert `Authorization: Bearer <key>` auf der HTTP-API |
| `WATERMARKS_GEMINI_*` | — | Entfernt Aug 2026: Google hat SynthID-Textwasserzeichen auf der API eingestellt (siehe `vendor-notes.md`) |
| `WATERMARKS_SYNTHID_SCORER_URL` | `wr-core` | Core auf das `wr-synthid-score`-Sidecar für SynthID-Bildbewertung zeigen lassen (z. B. `http://wr-synthid-score:8766` unter dem heavy-Profil) |
| `WATERMARKS_SYNTHID_SCORER_API_KEY` | `wr-core` + `wr-synthid-score` | Gemeinsamer Bearer-Schlüssel für das Scorer-Sidecar (leer = keine Authentifizierung) |
| `WATERMARKS_SYNTHID_TEXT_URL` | `wr-core` | Core auf das `wr-synthid-text`-Sidecar für SynthID-Textwasserzeichen zeigen lassen (z. B. `http://wr-synthid-text:8767` unter dem harness-Profil) |
| `WATERMARKS_SYNTHID_TEXT_API_KEY` | `wr-core` + `wr-synthid-text` | Gemeinsamer Bearer-Schlüssel für das Textwasserzeichen-Sidecar (leer = keine Authentifizierung) |
| `WATERMARKS_SYNTHID_TEXT_TIMEOUT` | `wr-core` | Sekunden, die auf das `wr-synthid-text`-Sidecar gewartet wird (Standard 120) |
| `WATERMARKS_MARKLLM_SCHEME` | `text_detectors.py` (Host) | MarkLLM-Schema für `/detect`: `kgw` (Standard) / `synthid` |
| `HF_TOKEN` | harness/heavy-Dienste | Hugging Face-Token für gated Modelle |
| `WATERMARKS_SERVICE_URL` | nur Client (Skill / curl) | Wo der Dienst erreichbar ist; Standard `http://127.0.0.1:8765` |
| `WATERMARKS_REWRITE_BACKEND` | `rewrite_text.py`-Hook | `print-prompt` (Standard) / `ollama` / `openai-compatible` |
| `WATERMARKS_REWRITE_MODEL` | `rewrite_text.py`-Hook | Modellname (z. B. `deepseek-v4-flash`) |
| `WATERMARKS_REWRITE_BASE_URL` | `rewrite_text.py`-Hook | API-Basis (z. B. `https://api.deepseek.com`) |
| `WATERMARKS_REWRITE_API_KEY` | `rewrite_text.py`-Hook | API-Schlüssel — nur per Umgebungsvariable, niemals auf argv |
| `WATERMARKS_REWRITE_ALLOW_REMOTE` | `rewrite_text.py`-Hook | `1`, um Nicht-Loopback-Endpunkte zuzulassen |
| `WATERMARKS_REWRITE_REASONING_EFFORT` | `rewrite_text.py`-Hook | `none` (Standard) / `low` / `medium` / `high` / `off` |
| `WATERMARKS_CLEAN_STRATEGY_FILE` | `server.py` `/clean` | Pfad zur Layer-B-Strategiekonfiguration JSON (Standard `config/clean_strategy.json`) |
| `WATERMARKS_GUMBEL_KEY` | `detect_gumbel.py` / `text_detectors.py` | Geheimer Schlüssel für keyed-Gumbel (EXP) Same-Key-Replay (z. B. `0x…`); bevorzugt gegenüber argv — wird niemals protokolliert |
**Layer B ist für die Textbereinigung erforderlich.** `/clean` wendet nach Layer A immer die Standardstrategie (aus `config/clean_strategy.json`, `{"default_strategy": "[email protected],[email protected]"}`) auf eine Textdatei an, es sei denn, die Anfrage übergibt ihre eigene `"strategy"`-Option (eine geordnete `tactic@intensity`-Liste). Ein Strategie-Schritt ist `tactic@intensity`; der `mlm`-Schritt benötigt `transformers` + `roberta-large`, und jeder LLM-Schritt (`paraphrase`, `humanize`, …) benötigt die `WATERMARKS_REWRITE_*`-Konfiguration. Wenn das erforderliche Backend/Modell nicht konfiguriert ist — oder keine Strategie verfügbar ist — **weist `/clean` die Anfrage mit einem 400 zurück**. Vorrang für den Konfigurationspfad: `--strategy-config` CLI-Flag > `WATERMARKS_CLEAN_STRATEGY_FILE` Umgebungsvariable > der Standard `config/clean_strategy.json`.
Images werden automatisch bei `v*`-Tags über [`.github/workflows/release-images.yml`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/.github/workflows/release-images.yml) veröffentlicht.
## Optionale SynthID-Pixelbewertung
`inspect_image.py` und `clean_image.py` können einen SynthID-Konfidenzwert
im Pixelbereich melden, wenn ein externer Checkout von
[`aloshdenny/reverse-SynthID`](https://github.com/aloshdenny/reverse-SynthID)
verfügbar ist. Der Scorer ist **nicht mitgeliefert**: Er wird zur Laufzeit aus
Ihrem Checkout geladen, und sein Code verbleibt unter der nicht-kommerziellen
Research License des Upstream-Projekts.
### Option 1: Ein-Befehl-Bootstrap (ohne Docker)```bash
SCRIPTS=service/scripts
# Clones upstream, creates a venv, and installs scorer-only dependencies.
"$SCRIPTS/setup_synthid.sh"
# Score an image (default checkout: ~/reverse-SynthID).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/score_synthid.py" shot.png
# Or surface the score from inspect / clean (same venv Python).
REVERSE_SYNTHID_DIR=~/reverse-SynthID \
~/reverse-SynthID/.venv/bin/python "$SCRIPTS/inspect_image.py" shot.png
setup_synthid.sh akzeptiert --dir PATH, --ref REF und --full (installiert die
vollständige Upstream-requirements.txt, was torch/diffusers für den
Upstream-VAE-Bypass hinzufügt, den dieses Projekt nicht verwendet).
Unter Windows verwenden Sie setup_synthid.ps1 (-Dir, -Ref, -Full), das die
venv unter .venv\Scripts\ erstellt — das Layout, nach dem image_meta.py bereits
bei os.name == "nt" sucht.
Option 2: lokaler Docker-Build```bash
make docker-synthid-build
Run unprivileged and with a read-only rootfs; the scorer only needs to read
/data and write to stdout/tmp.
docker run --rm
--user "$(id -u):$(id -g)"
--read-only --tmpfs /tmp
-v "$(pwd):/data"
watermarks-remover-synthid-scorer /data/shot.png
Das Image wird zur Build-Zeit lokal aus der Upstream-Quelle erstellt. Es wird nicht
veröffentlicht, sodass der Upstream-Code nicht weiterverbreitet wird.
### Option 3: HTTP-Scorer-Sidecar (docker compose)
Unter dem `heavy`-Profil führt der Compose-Stack den Scorer zusätzlich als HTTP-
Sidecar (`wr-synthid-score`) aus, damit der **veröffentlichte Core-Service** Bilder
vor/nach der Bereinigung bewerten kann, ohne den nicht-kommerziellen Upstream-
Code mitzubündeln. Richten Sie `wr-core` darauf aus und teilen Sie einen Bearer-Schlüssel (siehe `.env.example`):```bash
# .env
WATERMARKS_SYNTHID_SCORER_URL=http://wr-synthid-score:8766
WATERMARKS_SYNTHID_SCORER_API_KEY=change-me
docker compose --profile heavy up -d
Dann gibt POST /clean mit {"options": {"detect_before": true, "detect_after": true}} synthid_before / synthid_after im
Bericht zurück, und POST /detect auf einem Bild gibt den SynthID-Score zurück. Fail-Soft:
Wenn das Sidecar nicht erreichbar oder nicht konfiguriert ist, tragen Berichte
{"available": false, "error": ...} und die Bereinigung ist weiterhin erfolgreich.
V4-Scoring verwendet artifacts/spectral_codebook_v4.npz aus dem Upstream-Checkout
(`220 MB). Dies ist nur Erkennung/Scoring — es entfernt keine Pixel-
Wasserzeichen.
Optionale CtrlRegen-Pixelentfernung
Für Pixel-Domain-Bildwasserzeichen (SynthID-Klasse, StegaStamp, Tree-Ring,
StableSignature) führt ein optionales externes Backend die CtrlRegen-Pipeline
aus (ControlNet + DINOv2 IP-Adapter steuerbare Regeneration). Das Backend ist
mertizci/noai-watermark, eine
gepflegte Neuimplementierung der ICLR 2025
CtrlRegen-Methode mit automatischem Tiling.
Das Backend ist nicht mitgeliefert und enthält keine LICENSE-Datei, daher wird es als
alle Rechte vorbehalten behandelt: Es wird an einem festgelegten Commit geklont und zur Laufzeit geladen.
Seine Abhängigkeits-Pins aus der Forschungsära (requirements-ctrlregen.txt — z. B.
transformers==4.37.2, diffusers==0.27.2) tragen veröffentlichte Sicherheitshinweise und
sind absichtlich nicht aktuell, daher werden sie nur jemals innerhalb der
dedizierten venv installiert, die dieses Skript erstellt, und niemals in das Haupt-Service-Image;
setup_ctrlregen.sh überprüft auch den festgelegten Commit bei vorhandenen
Checkouts erneut, nicht nur bei frischen Klonen.
Bootstrap```bash
SCRIPTS=service/scripts
Clones upstream (pinned commit), creates a venv, installs torch + deps.
"$SCRIPTS/setup_ctrlregen.sh"
Standalone removal (default checkout: ~/noai-watermark).
NOAI_WATERMARK_DIR=~/noai-watermark
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_ctrlregen.py" shot.png -o shot.ctrlregen.png
Unter Windows verwenden Sie `setup_ctrlregen.ps1` (gleiche Flags wie `-Dir`, `-Ref`, `-Python`);
die venv landet in `.venv\Scripts\`, was `clean_image.py` bereits auflöst.
Es prüft die veröffentlichten PyTorch-Wheel-Indizes und wählt den höchsten aus, der
gleich oder niedriger als die von `nvidia-smi` ausgegebene CUDA-Version ist und tatsächlich existiert — diese Zahl
ist das Maximum, das der *Treiber* unterstützt, und Treiber sind abwärtskompatibel, sodass ein
Treiber, der 13.1 meldet (kein veröffentlichtes `cu131`), `cu130` installiert. Unterhalb der Compute-
Capability 7.5 erzwingt es `cu126`, den letzten Index, dessen Wheels noch
Maxwell/Pascal/Volta-Kernel enthalten. Es installiert `torch` **und** `torchvision`
gemeinsam aus diesem Index, damit die Abhängigkeitsinstallation sie nicht gegen CPU-
Builds von PyPI austauschen kann, und überprüft nach der Installation, dass `torch.cuda.is_available()`
true ist — wenn eine GPU erkannt wurde, aber torch am Ende nur CPU unterstützt, warnt das Skript
lautstark und beendet sich mit einem Fehlercode ungleich null, anstatt vorzutäuschen, dass die Einrichtung erfolgreich war.
### Aus `clean_image.py````bash
NOAI_WATERMARK_DIR=~/noai-watermark \
~/noai-watermark/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
-o shot.cleaned.png --remove-pixel ctrlregen
Reihenfolge der Operationen: zuerst Metadaten entfernen, dann CtrlRegen-Pixelentfernung, dann
ein optionaler Reverse-SynthID-Vorher/Nachher-Score (wenn REVERSE_SYNTHID_DIR
ebenfalls gesetzt ist).
Die Intensität ist standardmäßig konservativ (--ctrlregen-intensity 0.25), weil
eine höhere Intensität mehr Wasserzeichen entfernt, aber mehr vom Bild neu generiert.
Dokumentierte Presets: 0.15 minimal / 0.25 Standard / 0.35 ausgewogen /
0.5 aggressiv / 0.7 maximal (Backend-Standard ist 0.5). --ctrlregen-steps
ist standardmäßig 50 (effektive Denoising-Schritte ≈ Schritte × Intensität).
Bildgröße (native Grenze 512×512)
CtrlRegen ist ein 512×512 Stable Diffusion 1.5 ControlNet. Das Backend löst dies für beliebige Eingaben, sodass hier kein zusätzliches Tiling freigelegt wird:
- ≤512 px: einzelner Durchlauf — Mittelpunkt-Zuschnitt/Größenänderung auf 512, neu generieren, zurück skalieren.
- >512 px: automatisches überlappendes Tiling (512-px-Kacheln, 192 px Überlappung), Breite/Höhe auf Vielfache von 8 ausgerichtet, dann kosinus-überblendete Nähte.
- Beide Pfade: Die Ausgabe wird auf die Originalgröße skaliert und farblich an das Originalbild angepasst.
Sehr große Bilder (z. B. 4K) erzeugen viele Kacheln, sodass Läufe mit der Kachelanzahl skalieren (langsamer und höherer VRAM-Verbrauch). Große Eingaben nach Möglichkeit vorher herunterskalieren; Kachelgröße und Überlappung sind upstream fest codiert und werden nicht als Flags freigelegt.
Rechenleistung, gated Models und Verifikation
Rechnen Sie mit ~10 GB Modell-Downloads; eine GPU wird dringend empfohlen und CPU-Läufe
sind langsam. Einige Upstream-Modelle sind gated, daher HF_TOKEN exportieren (nur per Umgebungsvariable —
niemals per argv). clean_ctrlregen.py weigert sich, Abhängigkeiten automatisch zu installieren; zuerst
setup_ctrlregen.sh ausführen.
Es gibt keinen lokalen Detektor für StegaStamp/Tree-Ring/StableSignature, daher ist das
einzige lokale Signal der Reverse-SynthID-Score (ein Surrogat). Wenn verfügbar,
meldet clean_image.py --remove-pixel ctrlregen diesen Score vorher/nachher; die
offizielle Google SynthID-Prüfung bleibt die letzte Instanz.
Docker```bash
make docker-ctrlregen-build
docker run --rm -e HF_TOKEN="$HF_TOKEN"
--user "$(id -u):$(id -g)"
-v "$(pwd):/data"
watermarks-remover-ctrlregen /data/shot.png -o /data/shot.ctrlregen.png
## Optionale MarkLLM-Textwasserzeichen-Verifizierung
Für **kontrollierte Experimente** umschließt eine optionale externe Testumgebung
[`THU-BPM/MarkLLM`](https://github.com/THU-BPM/MarkLLM) (Apache-2.0), um Testtext
mit einem Wasserzeichen zu versehen und ihn nach einer Layer-B-Umschreibung
erneut zu erkennen — z. B. um zu beweisen, dass eine KGW-Markierung (Kirchenbauer,
Ihre „open-LLM"-Zeile) oder SynthID-Text (Gemini-Zeile) unter Ihrer Umschreibung
verschwindet. Es ist eine **Verifizierungs-Testumgebung, kein Orakel**:
Die MarkLLM-Erkennung ist nur gültig gegenüber der *selben* Scheme-Konfiguration
+ Schlüsseln, die bei der Generierung verwendet wurden, und sie kann nicht
bescheinigen, dass ein Vendor-Detektor fehlschlagen wird.
Das Backend ist **nicht mitgeliefert**. `setup_markllm.sh` klont das Upstream-Repository
an einem festgelegten Commit, erstellt eine venv und installiert festgelegte
Abhängigkeiten (torch + transformers); das Bewertungsmodell (Standard
`facebook/opt-1.3b`, Apache-2.0) wird beim ersten Lauf von Hugging Face
heruntergeladen.```bash
SCRIPTS=service/scripts
# Bootstrap (clones upstream, creates ~/MarkLLM/.venv, installs deps).
"$SCRIPTS/setup_markllm.sh"
# Generate watermarked + unwatermarked sample text under the KGW scheme.
MARKLLM_DIR=~/MarkLLM \
~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" watermark prompt.txt \
--scheme kgw -o wm.txt -o2 plain.txt
# Detect the scheme mark in a text file.
MARKLLM_DIR=~/MarkLLM \
~/MarkLLM/.venv/bin/python "$SCRIPTS/detect_text_watermark.py" detect wm.txt --scheme kgw --json
Verifikation rund um ein Layer-B-Rewrite: Übergeben Sie --markllm-scheme an
rewrite_text.py (mit --markllm-dir), und es zeichnet die MarkLLM-Erkennung
vor/nach sowie ein cleared-Flag auf:```bash
export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
MARKLLM_DIR=~/MarkLLM
python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt
--markllm-scheme kgw --markllm-dir "$HOME/MarkLLM" --json-stats
**Erkennungsgeführte iterative Umschreibung:** Layer B schreibt nun iterativ um und
stoppt, sobald ein Versuch die Evaluierung besteht. Jede Evaluierungsrunde erzeugt
`--candidates`-Varianten (Standard **1**, `WATERMARKS_REWRITE_CANDIDATES`)
und `--max-loops` begrenzt, wie viele Runden ausgeführt werden, bevor die Best-Effort-Variante
zurückgegeben wird (Standard **1**, `WATERMARKS_REWRITE_LOOPS`). Jede Variante ist ein
Umschreibungsaufruf plus eine Evaluierung, und eine Runde wird vorzeitig beendet beim ersten Versuch,
den der Evaluator als nicht wasserzeichenmarkiert meldet — daher wiederholt ein erhöhtes `--max-loops`
neue Varianten, bis eine Evaluierung besteht (eine typische saubere Umschreibung kostet einen
Versuch). Der Evaluator wird nach Priorität ausgewählt:
1. **MarkLLM** — Erkennung mit gleicher Konfiguration wie in der Forschung, wenn `--markllm-scheme`
übergeben wird (zusammen mit `--markllm-dir`). Ein Vendor-Detector-Slot ist oberhalb
von MarkLLM für Googles SynthID-Text-Detector reserviert, den Google im August 2026 in seiner API
eingestellt hat — ein zukünftiger Vendor-Endpunkt kann dort eingebunden werden.
2. **Bigram-Jaccard lexikalische Divergenz** — wenn kein Detector konfiguriert ist; kein
Bestanden/Nicht-bestanden-Urteil, daher wird jeder Versuch generiert und der lexikalisch
am stärksten divergierende ausgewählt (das ursprüngliche Verhalten).
`--json-stats` meldet den Evaluator, unternommene Versuche, Bestanden/Nicht-bestanden und
Datensätze pro Versuch:```json
{
"evaluator": "markllm",
"candidates": 1,
"max_loops": 2,
"attempts_made": 2,
"passed": true,
"candidate_scores": [
{
"lexical_divergence": 0.91,
"selection_score": 0.91,
"selected": false,
"passed": false,
"evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
"is_watermarked": true, "score": 4.3, "threshold": 3.0}
},
{
"lexical_divergence": 0.84,
"selection_score": 0.84,
"selected": true,
"passed": true,
"evaluation": {"detector": "markllm", "available": true, "scheme": "kgw",
"is_watermarked": false, "score": 1.7, "threshold": 3.0}
}
],
"markllm": {"scheme": "kgw", "before": {"...": "..."}, "after": {"...": "..."},
"cleared": true, "note": "same-config only"}
}
Ein Detektor, der nicht konfiguriert ist, ein Timeout auftritt oder einen Fehler zurückgibt, liefert einen
"available": false-Eintrag mit einem error-Grund und lässt das
Rewrite niemals fehlschlagen — dieser Versuch kann einfach nicht bestehen, und die Schleife fällt auf
die lexikalische Divergenzauswahl zurück. Wenn das Maximum ohne einen bestandenen Versuch ausgeschöpft ist, wird der
am wenigsten wasserzeichenbehaftete (niedrigste Punktzahl) Versuch als Best-Effort mit einem
Hinweis zurückgegeben.
Wenn das Backend nicht konfiguriert ist oder seine Abhängigkeiten fehlen, wird das Rewrite fortgesetzt und der Bericht vermerkt, dass die Verifizierung nicht verfügbar war. Eine GPU wird empfohlen; CPU- Läufe funktionieren, sind aber langsam, und der Modell-Download umfasst einige GB.
Hardening-Optionen:
--offlinebeim Adapter (oder bei jedem MarkLLM-Lauf) lädt das Scoring-Modell nur aus dem Hugging Face-Cache — kein Netzwerk-Egress; schlägt schnell fehl, wenn nicht im Cache. Benutzerdefinierter Remote-Code wird niemals ausgeführt (transformerstrust_remote_codeist niemals aktiviert).WATERMARKS_MARKLLM_RLIMIT_AS=<bytes>(env, POSIX) wendet ein Address-Space- Limit auf den MarkLLM-Detektor-Subprozess an. Standardmäßig deaktiviert, da torch/CUDA normalerweise große Address Spaces benötigt.- Konfigurationsdateien sind auf 1 MiB begrenzt; der Upstream-Checkout und das Basis-Image sind per SHA/Digest gepinnt.
Docker```bash
make docker-markllm-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data"
watermarks-remover-markllm detect /data/wm.txt --scheme kgw --json
### Keyed-Gumbel (Aaronson EXP) Same-Key-Verifikation
[ARBIs technischer Bericht](https://arbicity.com/news/ai-text-watermarking-for-self-hosted-ai/) beschreibt das
Keyed-Gumbel-("exponentielle") Textwasserzeichen — das jetzt in der Open-Source-
arbi-serve-Engine (`ARBI_WATERMARK_KEY`) ausgeliefert wird — bei dem das Rauschen des
Samplers aus einem geschlüsselten Hash des letzten 4-Token-Kontextfensters abgeleitet wird. Die Erkennung ist ein
**modellfreies Replay**: `u = PRF(Hash(key, window), token)` allein aus dem
Text neu berechnen und den Gamma-Tail testen, sodass weder GPU, Modell noch Logits benötigt werden.
Dieses Repo liefert diesen Detektor als `detect_gumbel.py` aus (nur stdlib; der p-Wert
ist die exakte Poisson-Summen-Identität für eine ganzzahlige Gamma-Form):```bash
# Text mode (deterministic word/run tokenizer) — quick checks and rewrite-loop
# evaluation; exact replay against a real engine needs its tokenizer:
python3 service/scripts/detect_gumbel.py draft.txt --key 0x... --json
# Exact replay: pass the engine's token ids (JSON array or one per line).
python3 service/scripts/detect_gumbel.py ids.json --tokens --key 0x... --json
Dieselbe Ehrlichkeits-Einschränkung wie bei MarkLLM: Dies ist ein Same-Key-Replay — gültig nur gegen denselben Schlüssel, Tokenizer und dasselbe PRF-Layout, das bei der Generierung verwendet wurde, und ein negatives Ergebnis belegt nichts. Das HMAC-SHA256-Layout hier ist eine auditierbare Instanziierung, nicht bitkompatibel mit irgendeinem spezifischen Engine-Kernel (siehe den Modul-Docstring, was für exaktes Replay angepasst werden muss).
Erkennungsgesteuertes Umschreiben: Übergeben Sie --gumbel-key an rewrite_text.py
(env: WATERMARKS_GUMBEL_KEY, bevorzugt), und die iterative Umschreibungs-Schleife wird
durch dasselbe Same-Key-Gumbel-Replay gesteuert — die Evaluator-Priorität wird zu gumbel >
MarkLLM > lexikalische Divergenz — mit einem gumbel.before/after/cleared-Bericht:```bash
export WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2
export WATERMARKS_GUMBEL_KEY=0x...
python3 "$SCRIPTS/rewrite_text.py" wm.txt -o wm.rewritten.txt --json-stats
Der Schlüssel erscheint niemals in Statistiken oder Logs. Selbstgehostete Betreiber, die den Schlüssel ihrer Engine besitzen, können verifizieren, dass ein Rewrite eine Gumbel-Markierung entfernt hat; alle anderen behandeln Layer B als reine Best-Effort-Maßnahme.
## Optionaler SynthID-Text-Entfernungs-Benchmark
[`bench_synthid_text.py`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/service/scripts/bench_synthid_text.py) misst, wie
effektiv ein Layer-B-Rewrite Watermarks der SynthID-Text-Klasse entfernt und
zu welchen Kosten. Es generiert gewasserzeichnete + nicht gewasserzeichnete
Samples mit dem MarkLLM-SynthID-Schema (Same-Config-Erkennung, sanity-gated),
führt deine Rewrite-Varianten aus (Taktik × maximale Rewrite-Versuche; die
Schleife stoppt früh bei Bestehen) plus Kontrollen (keine Entfernung, nur
Layer A, optionale Re-Stamp-Prüfung) und schreibt eine teilbare `report.md` /
`results.json` / `results.csv`. Vollständige Anleitung:
[`docs/synthid-text-benchmark.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/docs/synthid-text-benchmark.md).
Erfordert einen MarkLLM-Checkout (`setup_markllm.sh` / `MARKLLM_DIR`) und ein
Rewrite-Backend. **Das Rewriting-Modell ist ein LLM, das du konfigurierst** — dasselbe
`rewrite_text.py`-Backend, das der Skill verwendet. MarkLLMs Standard
`facebook/opt-1.3b` (`--markllm-model`) ist nur der Watermark-
Generator/Detektor; es führt niemals Rewrites durch. Konfiguriere das Rewrite-Modell über Env-
Variablen oder Benchmark-Flags (sie spiegeln die
[Konfigurationstabelle](#configuration-env-vars-for-docker-compose) oben):
| Env-Variable | Benchmark-Flag | Standard | Bedeutung |
| --- | --- | --- | --- |
| `WATERMARKS_REWRITE_BACKEND` | `--rewrite-backend` | `ollama` | `ollama` oder `openai-compatible` |
| `WATERMARKS_REWRITE_MODEL` | `--rewrite-model` | *(erforderlich)* | Das LLM, das den Rewrite durchführt (z. B. `llama3.2`, `deepseek-v4-flash`) |
| `WATERMARKS_REWRITE_BASE_URL` | `--rewrite-base-url` | `http://127.0.0.1:11434` | Endpunkt; der Ollama-Standard ist Loopback |
| `WATERMARKS_REWRITE_API_KEY` | `--rewrite-api-key` | — | API-Schlüssel (nur per Env im Kindprozess, niemals argv) |
| `WATERMARKS_REWRITE_ALLOW_REMOTE=1` | `--rewrite-allow-remote` | aus | Erforderlich, um Inhalte an Nicht-Loopback-Endpunkte zu senden |```bash
# Ollama (loopback):
python3 service/scripts/bench_synthid_text.py --markllm-dir ~/MarkLLM \
--rewrite-backend ollama --rewrite-model llama3.2
# OpenAI-compatible API (remote):
WATERMARKS_REWRITE_API_KEY=... python3 service/scripts/bench_synthid_text.py \
--markllm-dir ~/MarkLLM --rewrite-backend openai-compatible \
--rewrite-model deepseek-v4-flash --rewrite-base-url https://api.deepseek.com \
--rewrite-allow-remote
Verwende ein Nicht-Origin-Modell zum Umschreiben (schreibe nicht mit demselben
wasserzeichenversehenen Modell um, das den Text generiert hat), da das Umschreiben
die Ausgabe erneut stempeln kann; --restamp-control misst dies.
Optionale MarkDiffusion-Bildwasserzeichen-Harness
Für kontrollierte Experimente mit Bildern umschließt eine optionale externe Harness
THU-BPM/MarkDiffusion (Apache-2.0),
ein generatives Wasserzeichen-Toolkit für latente Diffusionsmodelle (es bettet Markierungen ein
— es entfernt sie nicht). Wir verwenden es für drei Dinge:
- Verifikations-Harness (wie MarkLLM, aber für Bilder): ein Testbild mit einem Schema mit einem Wasserzeichen versehen, Entfernung ausführen und mit der gleichen Schema-Konfiguration erneut erkennen — z. B. nachweisen, dass eine Markierung der Tree-Ring-Klasse unter deiner Pipeline verschwindet. Es ist eine Verifikations-Harness, kein Orakel: Die Erkennung erfordert das generierende Modell (und Schlüssel für schlüsselbasierte Schemata), daher kann sie nicht bestätigen, dass ein Anbieter-Detektor bei einem beliebigen Bild fehlschlägt.
- Optionale Pixel-Entfernungs-Engine: Ihr
DiffusionPurification-Regenerations- Angriff wird alsclean_image.py --remove-pixel diffusionbereitgestellt, eine Alternative zu CtrlRegen. Es ist blinde Regeneration (keine ControlNet- Konditionierung), daher driftet der Bildinhalt stärker als bei CtrlRegen — konservativer Intensitätsstandard (0.3), behandelt als Fallback/Vergleich, niemals als Garantie. - Lokaler Same-Scheme-Detektor für Markierungen der Tree-Ring-Klasse, der teilweise die Lücke „kein lokaler Detektor für StegaStamp/Tree-Ring/StableSignature" füllt (er deckt Tree-Ring/Ring-ID/Gaussian-Shading usw. ab, nicht StegaStamp / StableSignature / SynthID-media).
Das Backend ist nicht mitgeliefert. setup_markdiffusion.sh erstellt eine venv und
installiert markdiffusion==1.0.2 von PyPI (gepinnt), wobei torch vom
richtigen Plattform-Index installiert wird; --checkout installiert stattdessen einen editierbaren Clone an einem gepinnten
Commit. Das Stable Diffusion-Modell (Standard
huanzi05/stable-diffusion-2-1-base) wird beim ersten Lauf von Hugging Face heruntergeladen.```bash
SCRIPTS=service/scripts
Bootstrap (PyPI pin default; creates ~/markdiffusion/.venv, installs deps).
"$SCRIPTS/setup_markdiffusion.sh"
1. Generate a Tree-Ring watermarked image (+ unwatermarked control).
echo "a red fox in snow" > /tmp/prompt.txt
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" watermark
/tmp/prompt.txt -o wm.png -o2 plain.png --scheme tr --json
2. Remove with the DiffusionPurification regeneration attack.
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" purify
wm.png -o wm.purified.png --purification-intensity 0.3 --json
3. Re-detect with the SAME scheme config.
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" detect
wm.purified.png --scheme tr --detector-type l1_distance --json
Oder führen Sie die Bereinigung als Teil der normalen Image-Pipeline aus:```bash
MARKDIFFUSION_DIR=~/markdiffusion \
~/markdiffusion/.venv/bin/python "$SCRIPTS/clean_image.py" shot.png \
-o shot.cleaned.png --remove-pixel diffusion
Die Härtungsparameter spiegeln den MarkLLM-Harness wider: --offline lädt das Modell nur aus dem Hugging-Face-Cache (kein Netzwerk-Egress, kein Remote-Code), HF_TOKEN ist nur über Umgebungsvariablen (niemals über argv), Algorithmus-Konfigurationen sind auf 1 MiB begrenzt, und der Subprozess erhält dieselben höheren Ressourcenlimits wie CtrlRegen.
Docker```bash
make docker-markdiffusion-build
docker run --rm --user "$(id -u):$(id -g)" -v "$(pwd):/data"
watermarks-remover-markdiffusion detect /data/wm.png --scheme tr --json
Das Image installiert ein CPU-Torch; CUDA-Nutzer sollten stattdessen `setup_markdiffusion.sh` auf dem Host ausführen. Modell-Downloads treffen beim ersten Lauf weiterhin den HF-Hub.
## Abdeckungsmatrix
| Kanal | Claude | Gemini/SynthID | OpenAI | Open-LLM |
| --- | --- | --- | --- | --- |
| Unicode- / edit-basierter Text | Layer A | Layer A | Layer A | Layer A |
| **Statistischer Sampling-Text** | Layer B nach bestem Bemühen (Claude-Naht, sobald Anthropics Detection-API ausgeliefert wird) | Layer B nach bestem Bemühen (+ MarkLLM Same-Config-Harness; Google hat den Vendor-Detektor im August 2026 eingestellt) | Layer B, falls vorhanden | Layer B nach bestem Bemühen + optionaler MarkLLM-Harness |
| C2PA / Datei-Metadaten | Ja (aufgeführte Formate) | Ja, wenn vorhanden | Ja, wenn vorhanden | Ja, wenn vorhanden |
| Pixel-Bildmarkierungen | Außerhalb des Geltungsbereichs | Optionaler SynthID-Score + CtrlRegen-Entfernung (extern); optionale MarkDiffusion Same-Scheme-Erkennung + DiffusionPurification-Entfernung (extern) | Außerhalb des Geltungsbereichs | Optionale CtrlRegen- / MarkDiffusion-Entfernung (extern) |
| Trainings-Backdoors | Außerhalb des Geltungsbereichs | Außerhalb des Geltungsbereichs | Außerhalb des Geltungsbereichs | Außerhalb des Geltungsbereichs |
Details: [`skills/remove-ai-marks/references/vendor-notes.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/remove-ai-marks/references/vendor-notes.md), [`mark-classes.md`](https://github.com/guillaumemeyer/watermarks-remover/blob/main/skills/remove-ai-marks/references/mark-classes.md).
---
## Wie Textmarkierung funktioniert (kurz)
Moderne LLM-Wasserzeichen verstecken ein Signal oft darin, **welche Tokens gewählt werden** (generative / Sampling-Verzerrung), nicht nur in unsichtbaren Zeichen. Edit-basierte Verfahren injizieren Unicode- oder Synonym-Regeln. Datei-Verfahren hängen **C2PA**- oder Generator-Metadaten an.
- **Layer A** entfernt edit-basierte Unicode-Träger (testbar).
- **Layer B** greift Sampling-Wasserzeichen über umfangreiches Umschreiben an (nach bestem Bemühen; literaturübliche Angriffe wie Paraphrase / Rückübersetzung).
- **Datei-Cleaner** entfernen C2PA/XMP/Props aus unterstützten Containern.
Solange Anbieter keine öffentlichen Detektoren und Schlüssel ausliefern, kann **kein Tool ehrlich bescheinigen**, dass „dies die offizielle Prüfung nicht besteht“. Berichte müssen verifizierbare von Best-Effort-Arbeit trennen.
Bevorzugen Sie für Layer B ein **Nicht-Origin**-Modell (schreiben Sie Claude-Text nicht mit Claude um, wenn Sie eine erneute Stempelung vermeiden wollen).
---
## Haftungsausschluss: Was das Entfernen eines Textwasserzeichens kostet
Textwasserzeichen leben **in der Formulierung selbst**: Das Signal ist über Token-Entscheidungen verteilt, sodass nahezu jeder Satz ein wenig davon trägt. Daraus folgen zwei Konsequenzen, und sie sind der Grund, warum Layer B ehrlicherweise als *Best-Effort* und nicht als Zauberradiergummi beschrieben wird.
1. **Entfernung bedeutet Umformulierung, nicht Umstrukturierung.** Absätze umzuschichten, Überschriften zu ändern oder leichte Anpassungen bewegen das Signal kaum. Das Entfernen einer statistischen Markierung erfordert das Umschreiben eines erheblichen Teils des Textes — Satz für Satz, nicht Abschnitt für Abschnitt.
2. **Umformulierung verschlechtert den Text.** Jedes Umschreiben ersetzt die ursprünglichen Wortwahlen durch die des Umschreibungsmodells, was Ton, Stimme und Präzision einebnet. Bei Produktionstexten (SEO, Marketing, Kundenarbeit) ist diese Verschlechterung real und oft für diejenigen sichtbar, denen am meisten am Text gelegen ist. Es ist, als würde man Text von einem Spitzenmodell nehmen und ein weniger fähiges Modell bitten, ihn von Grund auf neu zu schreiben: Das Ergebnis kann die Obergrenze des Umschreibungsmodells nicht überschreiten.
Was zu der ehrlichen Frage führt, die den Kreis schließt:
> Wenn der Plan ohnehin ist, den Text mit einem günstigeren Modell umzuschreiben, warum dann überhaupt für ein Premium-Modell bezahlen? Direkt mit dem günstigeren Modell zu generieren ist einfacher, billiger und liefert dasselbe — oder ein besseres — Endergebnis.
Layer B ergibt Sinn, wenn Sie gezielt das **Denken und Entwerfen** des Premium-Modells wollen und einen Umschreibungsdurchlauf akzeptieren, um eine Hygiene- oder Datenschutzanforderung zu erfüllen — nicht als billiger Weg zu markierungsfreiem Text.
**Wann Layer B übersprungen werden sollte:**
- **Qualität ist wichtiger als Hygiene:** Verwenden Sie den verlustfreien Pfad — Layer A Unicode-Bereinigung plus die Datei-Metadaten-Cleaner — und behalten Sie die Originalprosa.
- **Ohnehin umschreiben:** Verwenden Sie ein **Nicht-Origin**-Modell (Umschreiben mit dem Origin-Modell kann den Text erneut stempeln), und denken Sie daran, dass ein Restrisiko bleibt — kein Tool kann bescheinigen, dass ein Vendor-Detektor fehlschlägt.
---
## Dateiformate
| Format | Inspizieren | Bereinigen |
| --- | --- | --- |
| PNG / JPEG / WebP | C2PA-Chunks / APP11 / RIFF `C2PA`, AI-XMP-Hinweise | Metadaten-Segmente entfernen |
| AVIF / HEIC | ISOBMFF `jumb` / XMP `uuid`-Boxen | Boxen entfernen |
| BMP | Angehängte Nicht-Bild-Bytes (kein standardisierter Kanal) | Angehängte Metadaten abschneiden, Dateigrößenfeld korrigieren |
| GIF | Kommentar- / XMP-Anwendungserweiterungen | Kommentar & XMP entfernen, `NETSCAPE2.0`-Schleife behalten |
| TIFF (klassisch + BigTIFF) | IFD-Tags: XMP, EXIF, GPS, IPTC, MakerNote | Tags entfernen, Payloads nullen, Strips behalten |
| SVG | `<metadata>`, XMP | Blöcke entfernen |
| PDF | Byte/XMP + optionale Tools | **exiftool**, dann **qpdf**, dann **ghostscript** für Metadaten in eingebetteten Bildern; jedes fehlende Tool degradiert eine andere Ebene (Dokument-Strip, strukturelles Umschreiben, eingebettete Bilder) |
| DOCX | docProps / customXml | Props bereinigen, customXml entfernen |
| EPUB | OPF-Metadaten, XHTML meta/JSON-LD, eingebettete Medien | OPF bereinigen, XHTML-Meta entfernen, Medien + Layer A bereinigen (überspringt verschlüsselte Teile) |
| ODT | meta.xml | Generator- / AI-artige Meta entfernen |
| HTML | meta, JSON-LD, data-ai* | Tags/Attribute entfernen |
| Markdown | YAML-Frontmatter-AI-Schlüssel | Schlüssel entfernen + Layer A auf den Body anwenden |
| MP4 / MOV / M4A / M4V | ISOBMFF `jumb`/`uuid`-Boxen (gleicher Mechanismus wie AVIF/HEIC) + `moov/udta`-Generator-Tags | Boxen entfernen |
| WAV | RIFF `C2PA` / `LIST INFO`-Chunks, eingebetteter `id3\x20`-Chunk | Chunks entfernen |
| MP3 | ID3v2-Frames (v2.3/v2.4 pro Frame; v2.2 ganzes Tag) | Passende Frames oder ganzes Tag entfernen |
| FLAC | C2PA-Manifest in einem ID3v2 `GEOB`-Frame | Passenden Frame oder ganzes ID3v2-Tag entfernen |
Die FLAC-Unterstützung deckt C2PAs standardisierten ID3v2-Träger ab. Native FLAC-Metadatenblöcke, Vorbis Comments und Wasserzeichen in der Wellenformdomäne bleiben unangetastet.
#### Warum PDF qpdf braucht, nicht nur exiftool
ExifTool schreibt PDFs **inkrementell**. `exiftool -all=` hängt einen `%BeginExifToolUpdate`-Block an, der das Info-Objekt freigibt und `/Info` aus dem Trailer entfernt — aber die ursprünglichen Metadaten-Bytes bleiben wörtlich in der Datei, und exiftool selbst kann die Bearbeitung mit `-PDF-update:all=` rückgängig machen. Der Befehl endet mit `0`, Betrachter zeigen keine Metadaten, und die Datei wird *größer*, was das verräterische Zeichen ist.
Für ein Tool zum Entfernen von Provenienz ist das ein stilles Leck, daher folgt `clean_pdf` dem exiftool-Durchlauf mit `qpdf --linearize`, was das Dokument aus seinem Objektgraphen neu serialisiert und die nun nicht mehr referenzierten Objekte entfernt. Ohne installiertes `qpdf` läuft die Bereinigung trotzdem, aber sie sagt es:```
warning: exiftool PDF edits are incremental — the original metadata bytes
remain recoverable; install qpdf for a structural rewrite
Warum qpdf für Bilder im PDF nicht ausreicht
Beide oben genannten Durchläufe arbeiten am Dokument: dem Info-Dictionary, dem XMP-Paket, dem Objektgraphen. Keiner steigt in ein Bild-XObject hinab, sodass ein Scan oder ein Photoshop-Export – eine Seite, die ein großes JPEG ist – alles behält, was das Bild mit sich trägt. Bei einem echten, aus Photoshop exportierten PDF bleiben nach einer „erfolgreichen" Bereinigung 27 Tags zurück, darunter IFD0:Software, die Aufnahmezeitstempel und ein Vorschaubild; ein C2PA-Manifest, das an dasselbe Bild angehängt ist, übersteht sie ebenfalls.
Daher fügt clean_pdf einen dritten Durchlauf hinzu, deep_images, angetrieben von Ghostscripts pdfwrite. Er läuft in zwei Stufen und stoppt, sobald die Datei sauber ist:
- Verlustfrei.
pdfwritemit Pass-Through baut das Dokument aus dem Objektgraphen neu auf und kopiert dabei die komprimierten Bilddaten Byte für Byte – verifiziert durch Hashen der Streams vorher und nachher. Dies entfernt alles, was das PDF um das Bild herum verpackt hat. Pass-Through deckt die Codecs ab, die Ghostscript dafür unterstützt, JPEG (DCTDecode) und JPEG2000 (JPXDecode); Flate-, CCITT- und LZW-Bilder werden dekodiert und neu kodiert, was für diese Codecs in der Praxis verlustfrei ist, aber nicht byte-identisch.neverist die Option für ein Dokument, dessen Streams unangetastet überleben müssen. - Neu kodieren, nur bei Evidenz. Alles, was in den eigenen APPn-Segmenten des JPEG lebt – EXIF in APP1, ein C2PA-Manifest in APP11, Photoshop-Ressourcen in APP13 –, wandert mit den Bytes, an die es angehängt ist, sodass Pass-Through es bewahrt. Stufe 2 führt denselben Durchlauf mit deaktiviertem Pass-Through aus, und nur dann, wenn Stufe 1 nachweislich etwas zurückgelassen hat: einen AI/C2PA-Marker in irgendeinem Modus oder, unter
always, jegliche überlebende APPn-Metadaten. APP0 (JFIF) und APP2 (ICC) bleiben unangetastet – das erste ist strukturell und das zweite entscheidet, wie die Farben gelesen werden. Pixel werden für Evidenz ausgegeben, niemals für Verdacht.
deep_images akzeptiert auto (Standard: Stufe 1 nur, wenn Marker den Dokument-Strip überlebt haben, dann Stufe 2, wenn sie auch das überleben), always (Stufe 1 für jedes PDF, mit Eskalation zu Stufe 2 auch für Kamera- und Editor-EXIF), lossless (nur Stufe 1 – niemals neu komprimieren, und melden, was auch immer überlebt, durch die üblichen Felder still_has_c2pa / post_findings) und never. Ein nicht erkannter Wert wird abgelehnt, statt stillschweigend als auto behandelt zu werden. Der Bericht gibt an, welche Stufen gelaufen sind, über meta.deep_image_pass und meta.images_reencoded, und wenn der Durchlauf übersprungen wird, nennt er die Option, die weiter gehen würde:```text
deep image pass not needed for AI/C2PA markers; pass deep_images="always"
to also clear non-AI EXIF inside images
Ohne installiertes Ghostscript läuft die Bereinigung trotzdem durch und meldet, was sie nicht erreichen konnte:```text
warning: metadata inside embedded images left in place; install ghostscript
for the deep image pass
Pixel-Domain-Wasserzeichen-Entfernung ist jetzt als optionales externes CtrlRegen-Backend verfügbar (siehe oben); es ist ein regenerierender Entferner, keine Garantie. C2PA-Soft-Binding (inhaltsinternes Wasserzeichen, das ein entferntes Content-Credentials-Manifest nach dem Strippen von Metadaten wieder verknüpfen kann) bleibt außerhalb des Geltungsbereichs. Das Strippen von hart gebundenem C2PA beseitigt diese Kanäle nicht.
Restrisiko nach einer Bereinigung
Dieses Tool meldet verifizierbare Entfernungen (Unicode-Zählungen, Metadaten-Aktionen) und Best-Effort-Layer-B-Umschreibungen. Es kann nicht zertifizieren, dass Anbieter-Detektoren fehlschlagen werden.
Um Rest-Signale selbst zu prüfen (optional, extern):
| Kanal | Was wir entfernen | Was verbleiben kann | Externe Prüfung (Beispiele) |
|---|---|---|---|
| Hart gebundenes C2PA / EXIF / XMP | Ja | Soft-gebundene / Pixel-Markierungen | c2patool, Content Credentials verify |
| SynthID-Klasse Medien | Optionale Pixel-Entfernung (externes CtrlRegen); andernfalls lokaler Score | Audio-/Video-Wasserzeichen; verbleibendes Pixel-Wasserzeichen nach Entfernung | Anbieter-Tools (z. B. Google SynthID / Vertex-Detektor, wo angeboten); optionaler lokaler reverse-SynthID-Scorer |
| Statistischer Text | Best-Effort-Umschreibung | Starke Markierungen nach leichter Bearbeitung | Kein öffentlicher universeller Detektor; Anbieter-Tools, wenn verfügbar |
Branchenkontext mit zwei Ebenen (C2PA + imperzeptibles Wasserzeichen): Institute of AI PM guide.
Wasserzeichen-Detektoren
Von Anbietern bereitgestellte Prüfer, um zu verifizieren, ob Inhalte KI-Herkunftsmarkierungen tragen:
- Claude: Check if a file was made with Claude — liest C2PA-Content-Credentials in Bildern, Videos und Audio aus, um zu melden, ob Claude an der Erstellung der Datei beteiligt war; läuft im Browser. Claudes Text-Wasserzeichen-Erkennungs-API befindet sich derzeit in privater Vorschau.
- OpenAI: Verify OpenAI-generated content — lade ein Bild oder eine Audiodatei hoch und prüfe auf OpenAI-Herkunftssignale (C2PA-Metadaten und SynthID-Wasserzeichen). Eine programmatische API ist ebenfalls verfügbar.
- Google DeepMind: SynthID — Googles Wasserzeichen-Technologie für KI-generierte Bilder, Audio, Text und Video, mit einer Übersicht darüber, wie die imperzeptiblen Markierungen eingebettet und erkannt werden.
- Gemini: Verify AI-generated images, videos, and audio — Googles Anleitung zur Verifizierung von Dateien in der Gemini-App mithilfe von SynthID-Wasserzeichen und Content Credentials, einschließlich Upload-Limits und zum Lesen der Ergebnisse.
Entfernungsoptionen (Zusammenfassung)
| Option | Entfernt | Hinweise |
|---|---|---|
| Unicode-Scrub (Layer A) | ZWSP, Bidi, Tags, exotische Leerzeichen, … | Sicherer Standard für Text |
| Rewrite (Layer B) | Statistische Token-Markierungen (Best-Effort) | Wird immer vom Skill angeboten; kostet Stil — siehe Disclaimer |
| Container-/Metadaten-Strip | Datei-Herkunft | Siehe Format-Tabelle |
| CtrlRegen-Pixel-Entfernung (optional) | Pixel-Domain-Bildmarkierungen (SynthID-Klasse, StegaStamp, Tree-Ring, StableSignature) | Externes Backend; hoher Rechenaufwand; konservative Intensitätsvoreinstellung |
| DiffusionPurification-Pixel-Entfernung (optional) | Pixel-Domain-Bildmarkierungen (Tree-Ring-Klasse) | MarkDiffusion-Backend; blinde Regeneration (mehr Drift als CtrlRegen); konservative Intensitätsvoreinstellung |
| Lokale Open-Weight-Modelle | Vermeidung von erneutem Stempeln mit dem Ursprungsmodell | Operative Alternative |
Matrix: skills/remove-ai-marks/references/removal-matrix.md.
Ethik und Disclaimer
Siehe skills/remove-ai-marks/references/ethics.md. Für Datenschutz und Forschung an deinen Inhalten — nicht für akademischen Betrug oder falsche „von Menschen geschrieben"-Behauptungen.
Verantwortungsvolle Nutzung: Dieses Projekt ist für Inhalte gedacht, die dir gehören oder deren Verarbeitung du autorisiert bist. Nutzer müssen lokale Vorschriften einhalten und es verantwortungsvoll verwenden. Die Entwickler lehnen jegliche Haftung für möglichen Missbrauch durch Nutzer ab.
Ökosystem
Drittanbieter-Projekte, die dieses Repository umhüllen oder ergänzen, nur zur Auffindbarkeit aufgeführt. Sie werden von diesem Projekt nicht gewartet, unterstützt oder befürwortet. Dieses Projekt überprüft deren Code nicht, bürgt nicht für deren Verhalten oder Garantien und übernimmt keine Verantwortung für irgendetwas, das du aus dieser Liste installierst oder ausführst. Jedes Projekt unterliegt seiner eigenen Lizenz, seinen Maintainern und seiner Dokumentation — lies diese, bevor du es verwendest.
MetaClean — Desktop-GUI
MetaClean ist eine unabhängige, MIT-lizenzierte Rust/Tauri-Desktop-Anwendung (Windows, macOS, Linux), die eine paketierte native GUI für Drag-and-Drop-Metadatenbereinigung mit System-Tray und Explorer-Integration bietet. Es ist eine separate Codebasis: Es ruft nicht den Python-Service dieses Repositorys auf, und seine unterstützten Formate und Bereinigungsgarantien unterscheiden sich von denen dieses Projekts. Siehe dessen README für Details.
unmark-web — Browser-Web-UI
unmark-web ist ein unabhängiger, MIT-lizenzierter statischer Web-Client. Er entfernt unsichtbare Unicode-Markierungen aus Text und strippt Herkunftsmetadaten aus Bildern vollständig im Browser und kann optional den HTTP-Service dieses Repositorys für die Formate aufrufen, die er nicht lokal verarbeitet. Es ist eine separate Codebasis und steht nicht mit diesem Projekt in Verbindung; siehe dessen README für Umfang und Grenzen.
DropMarks — macOS-GUI
DropMarks ist eine unabhängige, MIT-lizenzierte macOS-SwiftUI-Anwendung. Sie ruft inspect_file.py / clean_file.py dieses Repositorys (und optional rewrite_text.py) über einen vendorten Snapshot dieser stdlib-Skripte auf. Es ist eine separate Codebasis und steht nicht mit diesem Projekt in Verbindung; siehe dessen README für Umfang und Grenzen.
Ein Projekt hinzufügen
Um ein Projekt hier zu registrieren, öffne einen PR, der einen kurzen Eintrag hinzufügt — Projektname, was es umhüllt oder ergänzt, und einen Link zu seinem eigenen Repository. Halte Einträge kurz und sachlich; beanspruche keine Kompatibilität mit oder Befürwortung durch dieses Projekt. Ein gelistetes Projekt sollte auf diesem Repository aufbauen oder es integrieren — zum Beispiel durch Aufrufen seines Service oder Wiederverwenden seiner Erkennungs-Engine — statt lediglich dasselbe Problem unabhängig zu adressieren. Bitte vermeide Namen, die mit watermarks-remover beginnen oder diesem stark ähneln — ähnlich aussehende Namen machen es schwer zu erkennen, welches Projekt welches ist.
Pre-Commit-Hook
CI-Gating existiert bereits (SARIF-Export von audit_dir.py, siehe Kontext der Coverage-Matrix) — die pre-commit-Hooks unten fangen dieselbe Problemklasse früher ab, noch bevor eine markierte Datei überhaupt committet wird. Beide umhüllen die bestehenden CLIs (audit_dir.py / clean_file.py) — keine separate Erkennungslogik.```yaml
.pre-commit-config.yaml
repos:
- repo: https://github.com/guillaumemeyer/watermarks-remover
rev: v0.5.0 # pin to a tag/commit
hooks:
- id: watermarks-remover-check # fails the commit if marks are found
- id: watermarks-remover-clean # opt-in: cleans staged files in place instead
`watermarks-remover-check` lässt den Commit fehlschlagen und listet die Befunde auf; `watermarks-remover-clean` ist optional und schreibt gestagte Dateien direkt um (beendet mit 1, damit du den Diff überprüfen und erneut stagen kannst — dieselbe Konvention wie bei Auto-Fixing-Hooks wie `ruff --fix`). Wenn der Cleaner eine Datei überhaupt nicht verarbeiten kann — er abgestürzt ist, beendet wurde oder keinen Bericht erzeugt hat — benennt `watermarks-remover-clean` diese Datei und beendet mit 3, sodass ein fehlgeschlagener Cleaner niemals mit einer bereits sauberen Datei verwechselt wird. Führe beide manuell aus mit `python3 service/scripts/check_staged.py <files...>` / `clean_staged.py <files...>`.
## Tests```bash
python3 -m venv .venv && .venv/bin/pip install pytest
.venv/bin/python -m pytest # or: make test
make smoke # quick CLI smoke on fixtures
Changelog
v0.7.0 — /clean Layer-B-Neufassung, Wasserzeichen-Stealing-Modul, Audio-/Video-Wasserzeichen-Entfernung und breitere Benchmark-/Tooling-Abdeckung
v0.7.0 bringt die statistische Layer-B-Mark-Neufassung direkt in den /clean-Dienst, angetrieben von einer konfigurierbaren, benchmark-abgestimmten Strategie ([email protected],[email protected]). Daneben: ein Black-Box-Wasserzeichen-Stealing-Modul, destruktive Audio- und Per-Frame-Video-Wasserzeichen-Entfernung, ein deutlich umfangreicheres Rewrite-Benchmark und eine Reihe von Härtungs-, Sicherheits- und Tooling-Fixes.
Layer-B-Neufassung im Dienst
/cleanführt die Layer-B-Neufassung für Text nach Layer A aus. Der Standard kommt ausconfig/clean_strategy.json; ein per-Requestoptions.strategyüberschreibt ihn, und/cleanlehnt mit 400 ab, wenn das erforderliche Backend nicht konfiguriert ist (#315). Konfigurationspriorität:--strategy-config>WATERMARKS_CLEAN_STRATEGY_FILE>config/clean_strategy.json.- Neue
mlm-Rewrite-Taktik: Maskierung eines Bruchteils der Inhaltswörter und Auffüllen mitroberta-large— eine nicht-autoregressive lokale Bearbeitung, sodass die Ausgabe den ursprünglichen Token-Stream mit Masked-LM-Vorhersagen vermischt (#311). - Die
humanize-Taktik wendet nun den Humanizer-Skill-Durchlauf deterministisch an (gerade Anführungszeichen, keine en/em-Dashes, Filler-Kollaps,utilize→use) und benennt die Human-Writer-Regeln im Prompt (#311).rewrite_text.pyerhielt einen--strategy-CLI-Pfad. - Rewrite-Korrektheit: Unicode-Wort-Tokenisierung bei lexikalischer Divergenz (#305); Vergleich roher Margen vor der Rundung und Aufzeichnung von Auswahl-Metadaten / rangierten p-Werten (#249).
Benchmark
- SynthID-Rezeptsuche + robuste Messung (#280); umbenanntes Rewrite-Vokabular, Cross-Input-Suche und Humanize-Last-Reihenfolge (#302); Empfehlung nur von Strategien, die nach der Humanize-Politur noch bestehen (#307).
- Pangram-Bulk-API als Human-Likeness-Backend (#296); gehärtetes Minimal-Rewrite-Level-Benchmark mit einem 30-Dokumente-Korpus (#257); validiertes Gewichtsraster + erweiterte Rezeptsuche (#294); polnischer Benchmark-Korpus (#295).
Wasserzeichen-Stealing
- Neues Black-Box-Wasserzeichen-Stealing-Modul und Prompt-Korpus-Downloader (#303); Zurücksetzen veralteten Zustands bei Start-over-Probe-Fehler (#310).
Audio / Video / Bild
- Destruktive Audio-Wasserzeichen-Entfernungskette für silentcipher/AudioSeal/WavMark (Tempo + Pitch + EQ + Low-Bitrate-Re-Encode → M4A) (#266).
- Per-Frame-TrustMark-Video-Bereinigung, die die zeitliche Abstimmung kollabiert (#265).
- C2PA-Content-Provenance-
uuid-Box auf MP4/MOV/AVIF/HEIC erkannt (#264). - Erhalt abgeschnittener MP4-Tails beim Stripping (#242); Audio-Re-Encode-Ziel vom Container-Clean-Ziel getrennt halten (#278).
- Überspringen verworfener exiftool-Ausgaben und redundanter SynthID im Post-Clean-Scan (#261); sauberes Degradieren, wenn exiftool eine PDF nicht verarbeiten kann (#281).
- Begrenzung dekomprimierter PNG-
zTXt/iTXtauf 1 MiB (#308); Entfernen von SVG-XML-DOCTYPE/ENTITY-Deklarationen (#288); DOCX-Binär-Member byte-sicher halten (#314); Erhalt von OOXML-AppVersion(#289).
HTTP-Dienst & CLI
/clean-Option zum Beibehalten exotischer Leerzeichen, analog zur CLI (#274);/inspectlegt explizite Evidenzklassen in der verdächtigen Payload offen (#277); Zeitstempel in HTTP-Request-Logs (#256); Thread-Payload-Bytes in HTTP-SynthID-Scoring undinspect_*einfließen lassen, um ein redundantes Read-back zu vermeiden.clean_file.pyerhielt-q/--quiet/--only-changed(#254).
Skills, Plugin & Hooks
- Stylometrie-Scoring und Detektor-Hebel für
clean-user-facing-text(#258); PostToolUse-Hook-Launcher plattformübergreifend gemacht (#255); Pre-Commit-Hook behandelt byte-identische saubere Nicht-Text-Dateien als geändert (#238).
Audit
audit_dir.pyscannt Quell-, Doku- und i18n-Dateien, die der Router übersehen hat (#284); scannt.ts/.tsx/.jsx/.gdund gleicht Space-Confidence über Formate hinweg ab (#273);audit_website.py --sarif-Unterstützung (#194); Härtung von In-Place-Backups, Clean-File-Status, SynthID-Verdict, abgeschnittenem ID3v2 und Zip-Routing (#201).
Sicherheit
- Entfernen von polynomiellem ReDoS in Data-URI- und JSON-LD-Scans (#306); Blockieren von HTTP-Redirects im SynthID-Scorer zur Verhinderung von SSRF (#252).
CI, Tooling & Doku
- CI schlägt fehl, wenn optionale Backend-Anforderungen nicht aufgelöst werden können (#301); Docker-Image meldet ffmpeg als nutzbar und installiert Ghostscript (#272); Dependency-Bumps (cython #299, scipy #298, ruff #297, docker/setup-buildx-action #237).
- Doku: Watermark-Detectors-Abschnitt, ETH-SRI-„Probing SynthID"-Blog-Referenz, Ecosystem-Richtlinie (ClaudeWatermarks entfernen; gelistete Projekte müssen dieses Repo verwenden) (#292).
v0.6.0 — breitere Formatabdeckung, Layer-A-Härtung, Plugin- & Hook-Verteilung und detektionsgesteuerte Neufassung
Format- & Container-Abdeckung
- AVIF / HEIC: native stdlib-Metadaten- und C2PA-Entfernung (#84, #85)
- BMP / GIF / TIFF: stdlib-Erkennung, -Inspektion und Metadaten-Bereinigung — GIF-Kommentar-/XMP-Erweiterungen werden verworfen, während
NETSCAPE2.0-Looping und andere Animations-Chunks erhalten bleiben; TIFF-IFD-Metadaten (XMP/EXIF/GPS/IPTC/MakerNote) werden verworfen, wobei Payloads genullt und Strip-Offsets beibehalten werden, sowohl für klassisches TIFF als auch BigTIFF; BMP-Trailing-Metadaten werden abgeschnitten und das Dateigrößenfeld neu geschrieben (#107) - EPUB: stdlib-Container-Bereinigung — OPF-Metadaten und XHTML-Meta/JSON-LD gescrubbt, eingebettete Raster-/SVG-Medien entfernt, Layer A auf XHTML-Body-Text angewendet, marker-tragende Metadaten-Teile verworfen und OCF-verschlüsselte Teile unangetastet durchgereicht (#107)
- XLSX / PPTX / DOCX (OOXML): native stdlib-Container-Metadaten-, Text- und eingebettete-Medien-Bereinigung; DOCX-
docProps-Provenance-Felder immer leeren; hängende Relationships nachcustomXml-Entfernung bereinigen; Layer A über DOCX/ODT-Body-Text laufen lassen; XML-Entities vor Layer-A-Scrub dekodieren (#91, #100, #76, #83, #73, #80, #74, #81, #142) - SGML/Vektor-Container: Linearzeit-Metadaten-Stripping für SVG/ODT (GHSA-7vpp-96qp-j9wh) (#147); rekursive Inspektion und Bereinigung eingebetteter Raster-Data-URIs in SVGs, HTML und Markdown (#87, #88)
- Audio / Video: KI-/C2PA-Metadaten-Stripping für MP4/MOV, WAV und MP3 (#139); WAV-RIFF-C2PA-Chunk-Erkennung und -Entfernung; FLAC-C2PA-Metadaten-Unterstützung; Ablehnung partieller ID3v2-Frame-Parsing (#232); Erhalt von MP4-Medien-Offsets beim Metadaten-Stripping (#183)
- PDF: Metadaten erreichen, die in eingebetteten Bildern liegen, und aufhören, die PDF zum XMP-Stripping zu skalieren; den Deep-Image-Durchlauf unabhängig davon ausführen, ob exiftool installiert ist; JPEG-Marker-Füllbytes berücksichtigen und einen gemeinsamen Segment-Walker teilen
- PNG: KI-Generator-Produktnamen in PNG-Text-Metadaten erkennen; KI-Marker in komprimiertem PNG-Text erkennen (#127); den abgeschnittenen Tail behalten, statt ihn bei png/isobmff-Strips zu verwerfen (#182)
Layer A (unsichtbares Unicode) Härtung
- Konsolidierte Layer-A-Härtung (#133): Entfernen reservierter
Default_Ignorable-Codepoints ohne legitime Interchange-Nutzung (U+2065,U+FFF0–U+FFF8,U+E0000,U+E0080–U+E00FF,U+E01F0–U+E0FFF— gemeldet alsreserved_ignorable), der 66 Noncharacters (U+FDD0–U+FDEFplusU+FFFE/U+FFFFpro Plane — gemeldet alsnoncharacter) und drei blank-rendernde Default_Ignorable-Träger, die derCf-Catch-all nie sah (U+180F,U+3164,U+FFA0). Jeder hat dieselbe kontextbezogene Bewahrung wie seine bereits abgedeckten Geschwister, sodass Teil-Silben-Text nicht beschädigt wird, und jeder wird sowohl auf die Service-Engine als auch auf die vendored Lightweight-Skill-Kopie angewendet - Kein Stripping sichtbarer Layout-Format-Steuerzeichen neben ihrem eigenen Skript mehr: ägyptische Hieroglyphen-Quadrat-Steuerzeichen (
U+13430–U+1343F), Duployan-Kurzschrift-Steuerzeichen (U+1BCA0–U+1BCA3) und musikalische Beam-/Tie-/Slur-/Phrase-Steuerzeichen (U+1D173–U+1D17A) bleiben nun erhalten, wenn sie an ihr eigenes Skript angrenzen, und werden weiterhin entfernt (und markiert), wenn sie zwischen unzusammenhängendem Text schweben; der--strip-emoji-glue-Paranoid-Modus entfernt sie weiterhin überall - Emoji-/Skript-Politur: VS16 nach Emoji-Singletons außerhalb der Blockbereiche erhalten; Skript-Joiner, Flaggen-Emoji und arabische Cf-Marken erhalten; mehrsprachiges Unicode während der Textbereinigung erhalten (#34)
Layer-B-Neufassung & Wasserzeichen-Erkennung
- Iterative, detektionsgesteuerte Layer-B-Neufassung: jede Runde generiert
--candidates-Varianten (Standard 1,WATERMARKS_REWRITE_CANDIDATES) und--max-loops(Standard 1,WATERMARKS_REWRITE_LOOPS) begrenzt die Evaluierungsrunden und stoppt, sobald ein Versuch die Detektion besteht. Evaluator-Priorität: MarkLLM (--markllm-scheme) > Bigram-Jaccard-lexikalische Divergenz (Fallback).rewrite_text.py --json-statsmeldet nunevaluator/max_loops/attempts_made/passedund pro Versuchcandidate_scores(#153) - Keyed-Gumbel (Aaronson EXP) Same-Key-Verifikation: neues stdlib-only
detect_gumbel.pyimplementiert den modellfreien Replay-Test (u = PRF(Hash(key, window), token); exakter Gamma-Tail-p-Wert; Repeated-Window-Masking) ohne GPU, Modell oder Logits.rewrite_text.py --gumbel-key(envWATERMARKS_GUMBEL_KEY, bevorzugt) macht es zum Iterative-Loop-Evaluator (Priorität: gumbel > markllm > lexikalische Divergenz) und es wird alsgumbelin/capabilitiesund/detectexponiert. Nur Same-Key — kein Vendor-Oracle; der Key wird nie geloggt (#190) - Benchmarks: Multi-Scheme-MarkLLM-Text-Benchmark und -Detektion (#188) und ein reproduzierbares SynthID-Text-Entfernungs-Benchmark (#145); Standard-Varianten
paraphrase:3; Report und CSV tragen Versuche pro Dokument (mean_attempts/att,attempts/evaluator/passed-Spalten);--rewrite-loopsspiegelt--max-loops - Detektion: Vendor-Text-Wasserzeichen-Erkennung (Gemini SynthID, Claude Seam, MarkLLM) plus ein SynthID-Bild-Scorer-Sidecar (#109); neuer Zero-LLM-statistischer und stylometrischer KI-Text-Detektor für CI und Audits (#68, #69)
Verteilung: Plugin-, Hook- und Skill-Installationen
- Das Repository ist jetzt ein Claude-Code-Plugin und ein Single-Plugin-Marketplace (
.claude-plugin/plugin.json+marketplace.json), sodass beide Skills mit/plugin marketplace add guillaumemeyer/watermarks-removerund dann/plugin install watermarks-remover@watermarks-removerinstalliert und in-place aktualisiert werden.make plugin-validateführtclaude plugin validate . --strictaus;tests/test_plugin_manifest.pyprüft die Manifeste ohne die CLI install_skill.pyerhielt ein--target(claude-code,claude-project,cowork,cursor) und einen--skill-Selektor, der beide mitgelieferten Skills abdeckt, plus--list,--linkundCLAUDE_CONFIG_DIR. Dascowork-Target baut ein reproduzierbares Upload-Bundle (dist/<skill>.zip, einzelnes Top-Level-Skill-Verzeichnis); jedes Target validiert gegen die Agent-Skills-Packaging-Regeln und das 30-MB-Upload-Limit. Neuemake-Targets:install-claude-code-skill,install-claude-code-text-skill,install-claude-project-skill,package-cowork-skill,package-cowork-text-skill- Deterministische Auto-Bereinigung über einen
PostToolUse-Hook (hooks/hooks.json+service/scripts/hook_written_file.py): nachdem der Agent eine Datei schreibt, führt das Harness den Hook aus, unabhängig davon, ob das Modell kooperiert.check(Standard) meldet Marken an das Modell;cleanentfernt sie in-place und teilt dem Modell mit, dass die Datei verschoben wurde, wobei nur bei einer echten Differenz getauscht wird, sodass saubere Dateien ihre mtime behalten. Der Modus kommt aus derhook_mode-Einstellung des Plugins oderWATERMARKS_HOOK_MODE; die Erkennung verwendetaudit_lib.scan_file/is_actionablewieder, sodass der Hook, das Pre-Commit-Gate und der CI-SARIF-Export übereinstimmen. Ein Hook kann die Chat-Nachricht des Assistenten weiterhin nicht umschreiben — ein solcher Hook-Punkt existiert nicht —, sodass dieser Pfad Best-Effort bleibt - Pre-Commit-Hook-Integration für Staged-File-Prüfung/-Bereinigung (#138); leichtgewichtiger Cursor-Text-Skill (#35); die Beschreibung von
clean-user-facing-textnennt Cursor nicht mehr als einzigen Host
HTTP-Dienst
- Batch-Endpunkte:
POST /clean/batch,/inspect/batch(#137) undPOST /detect/batch(#151) - Bildformat-Erweiterungen in
/cleanerhalten und sichere Schreibvorgänge inav_metaverwenden (#150); portables base64 im/detect-curl-Beispiel verwenden (und die macOS-realpath-Portabilität in den Bootstraps beheben, #185)
Audit / Inspektion & Sicherheit
audit_dir.pyerhielt Multi-Worker-Concurrency und SARIF-2.1.0-Export (#101, #102)- Website-Binärformate an ihre echten Scanner routen (#177); DTD-/Entity-Bombs im Sitemap-Parser ablehnen (GHSA-pjg6-92pm-mmcf) (#146); ein abgestürzter Cleaner blockiert den Commit, statt als sauber gelesen zu werden (#179); eine unlesbare Textdatei ist ein fehlgeschlagener Scan, kein sauberer (#169)
Zuverlässigkeits- & Korrektheits-Fixes
- Ein zweiter
--in-place-Lauf erhält das ursprüngliche.bak; gesammelte Evidenz behalten, wenn ein späteres Zip-Member nicht gelesen werden kann (#175); abgeschnittene ISOBMFF-Container führen weiterhin den C2PA-Byte-Scan-Fallback aus (#176); einen fehlgeschlagenen Cleaner von einer bereits sauberen Datei unterscheiden (#159, #161); einen fehlgeschlagenen c2patool-Lauf als unschlüssig statt als „kein C2PA" behandeln (#156); Clean-Optionstypen validieren (#111); niemals automatisch MPS-Device für Text-Wasserzeichen-Erkennung auswählen (#99); macOS-Portabilität — reines--json-stdout für den SynthID-Scorer und BSD-realpath-Probe (#70); einen Windows-subprocess_creationflags-Pfad in_ghostscript_usablebeheben und verhindern, dass Kindprozesse unter Windows ein Konsolenfenster öffnen - Verhaltenshärtung: benignen JPEG-Kommentar-Keep-Modus erhalten; das verschluckte Flag in
bench-synthid-textbeheben; Flag-Passthrough für die Ghostscript-Probe vereinfachen und clean_text unnötiges noqa (Lint)
CI / Tooling / Doku
- Ruff-Linting und -Formatierung mit CI-Erzwingung (#103); macOS zur Testmatrix hinzufügen (#152); eine CodeRabbit-Konfiguration für automatisierte PR-Reviews hinzufügen (#222); CODEOWNERS für CODE_OF_CONDUCT/LICENSE und Main-Review-Owner; Copyright Guillaume Meyer und Mitwirkenden zuschreiben (#228)
- Doku: stimmungserhaltende Rewrite-Anleitung und Schutz von Stimm-/Barrierefreiheits-Entscheidungen; Ecosystem-Ergänzungen (ClaudeWatermarks, unmark-web) und ein Hinweis, der von ähnlich klingenden Namen abrät; arXiv-2402.14904-Referenz; Windows-Autostart-Anleitung via Task Scheduler; portables base64 in curl-Beispielen; die vendored Cursor-Skill-Text-Engine an die Service-Kopie pinnen (#96)
Unreleased
- Pre-Commit-Clean-Hook (
watermarks-remover-clean/clean_staged.py): Content-Digests (SHA-256) und aktive Action-Erkennung verwenden, damit saubere Dateien auf der Festplatte erkannt werden, ohne ein endloses Re-Staging zu verlangen (#173) - OOXML-Container-Erhalt:
<AppVersion>indocProps/app.xmlwährend der DOCX-, XLSX- und PPTX-Metadaten-Bereinigung intakt halten, um ECMA-376-Schema-Constraints zu erfüllen und Microsoft-Word/Office-„unreadable content"-Fehler zu vermeiden (#283)
v0.5.0 — Service- & Docker-Verteilung, HTTP-API und Verifikations-Harnesses
Service- / Docker-Verteilung
- Skill-/Service-Split: der Skill (
skills/remove-ai-marks/) ist jetzt ein codefreier Remote-Client über HTTP; die gesamte Implementierung wurde nachservice/scripts/verschoben und läuft hinterserver.py, einem stdlib-HTTP-Einstiegspunkt (/health,/inspect,/clean,/capabilities) - HTTP-Dienst:
service/scripts/server.pyexponiert die Bereinigungspipeline über JSON/base64; die Härtung spiegelt die CLIs (Größenobergrenzen, Binär-Guard, atomare Schreibvorgänge, Loopback-Standard, optionaleWATERMARKS_SERVER_API_KEY-Bearer-Auth) - OpenAPI:
GET /openapi.jsonliefert eine dynamisch generierte OpenAPI-3.0.3-Spezifikation (aus der Routentabelle + Live-Konfiguration erstellt, sodass sie nie von den echten Endpunkten abweicht); CI validiert sie mitopenapi-spec-validator - Core-Docker-Image (
service/Dockerfile): vollständiger Bereinigungsdienst mit vorinstalliertem exiftool / qpdf / c2patool; jede CLI bleibt durch Überschreiben des Befehls ausführbar - Docker / Compose:
compose.yamlbringt die gesamte Infrastruktur hoch (coreimmer;markllm/markdiffusionhinterprofile: harness;ctrlregen/synthidhinterprofile: heavyals Local-Only-Builds); Dienste sind mitwr-präfixiert; Harness-/Heavy-Dienste verwenden standardmäßigcommand: ["--help"], sodassdocker compose up --profile harness --profile heavysauber beendet wird (One-Shot-CLIs werden mitdocker compose runausgeführt); neuesmake compose-check/compose-check.shvalidiert den laufenden Stack (nur Exit-Code) - GHCR-Publishing:
.github/workflows/release-images.ymlveröffentlichtcore-,markllm-,markdiffusion-Images beiv*-Tags;ctrlregen/synthidwerden nie veröffentlicht (Upstream-Lizenzierung) - Env-Konfiguration:
.env.example+ Service-Konfigurationsanleitung;docker composelädt.envautomatisch;.envist gitignored (Deny-by-Default) - Repo-Hygiene:
.gitignoreundservice/.dockerignoresind jetzt Deny-by-Default — nur explizit erlaubte Pfade können committet oder in einen Build-Kontext gesendet werden (Image-Kontexte liefern nurservice/scripts/, was alles ist, was die Dockerfiles COPYen) - Tests:
tests/test_http_server.py(13 Fälle) für den HTTP-Dienst; alle Suiten aufservice/scripts/umgestellt
MarkDiffusion-Bild-Wasserzeichen-Harness (optional)
- Neuer optionaler Harness (externes
THU-BPM/MarkDiffusion, Apache-2.0):markdiffusion_harness.pymitwatermark- /detect- /purify-Subcommands für neun Bild-Schemata (Tree-Ring, Ring-ID, ROBIN, WIND, SFW, Gaussian-Shading, GaussMarker, PRC, SEAL) clean_image.py --remove-pixel diffusionführt den MarkDiffusion-DiffusionPurification-Regenerationsangriff als alternative Pixel-Entfernungs-Engine aus (konservative Intensität 0.3 Standard)setup_markdiffusion.sh-Bootstrap (PyPI-Pin1.0.2;--checkouteditierbarer Clone beim gepinnten Commit) +requirements-markdiffusion.txt+Dockerfile.markdiffusionund Makefilebootstrap-markdiffusion/smoke-markdiffusion/docker-markdiffusion-build/docker-markdiffusion-help- Mock-basierte Tests (
tests/test_markdiffusion_harness.py) — kein torch in CI;references/markdiffusion.md-Referenzdokument - Doku: Same-Scheme-Only-Verifikations-Caveat (kein Vendor-Detector-Oracle) und Blind-Regeneration-Drift-Caveat in README, SKILL.md,
removal-matrix.md,markdiffusion.md
MarkLLM-Text-Wasserzeichen-Harness (optional)
- Neuer optionaler Harness (externer
THU-BPM/MarkLLM-Checkout, Apache-2.0):detect_text_watermark.pymitdetect- /watermark-Subcommands für KGW- und SynthID-Schemata rewrite_text.py --markllm-schemeführt Before/After-Detektion um eine Layer-B-Neufassung herum aus und Per-Candidate-Detektion, wenn--candidates N>1(env-gated; meldetcleared)setup_markllm.sh-Bootstrap +requirements-markllm.txt(gepinnte Deps) +Dockerfile.markllmund Makefilebootstrap-markllm/smoke-markllm/docker-markllm-build/docker-markllm-help- Härtung:
--offlineCache-Only-Modell-Loading (kein HF-Egress, kein Remote-Code), 1-MiB-Config-Cap, optionalesWATERMARKS_MARKLLM_RLIMIT_ASauf dem Rewrite-Subprozess, gepinntes torch im Dockerfile und Clone-SHA-Verifikation inDockerfile.markllm - Mock-basierte Tests (
tests/test_markllm_detect.py, 21 Fälle) — kein torch in CI; Verifikations-Harness-Caveat (Same-Config-Only, kein Vendor-Detector-Oracle) dokumentiert in README, SKILL.md,removal-matrix.md,vendor-notes.md
Fixes und Politur- Layer B: rewrite_text.py sendet jetzt standardmäßig reasoning_effort: "none" für openai-compatible-Backends (--reasoning-effort / WATERMARKS_REWRITE_REASONING_EFFORT; off lässt es weg). Reasoning-Modelle wie deepseek-v4-flash verbrennen sonst ~100s Chain-of-Thought für einen einzeiligen Rewrite (9.894 vs. 12 Completion-Tokens)
- markllm-Image-Build repariert:
requirements-markllm.txtpinntetokenizers==0.23.1, was mittransformers==5.15.0kollidiert (begrenzt auftokenizers<=0.23.0; es existiert kein 0.23.0-Release) — jetzt auftokenizers==0.22.2gepinnt; torch auf den CPU-Wheel-Index verschoben (torch==2.13.0.*), damit das Image wieDockerfile.markdiffusionnur CPU nutzt - ctrlregen-Image-Build repariert: die Research-Pins aus der 2023er-Ära (
safetensors==0.4.3,transformers==4.37.2→tokenizers<0.19) liefern keine Python-3.14-Wheels, daher ist das Basis-Image jetztpython:3.11-slim(digest-gepinnt, Multi-Arch) - Harness-Images zur Laufzeit repariert:
Dockerfile.markllmundDockerfile.markdiffusionkopiertencommon.pynie nach/app(vorbestehender Bug) — hinzugefügt - WebP: stdlib-only Inspektion und Metadaten-Bereinigung für RIFF-
C2PA-, XMP-, EXIF- und ICC-Profil-Chunks (#37) - BMP / GIF / TIFF: stdlib-only Erkennung, Inspektion und Metadaten-Bereinigung — GIF-Kommentar-/XMP-Erweiterungen werden entfernt, während
NETSCAPE2.0-Looping erhalten bleibt; TIFF-IFD-Metadaten (XMP/EXIF/GPS/IPTC/MakerNote) werden entfernt, Payloads genullt und Strip-Offsets beibehalten, sowohl für klassisches TIFF als auch BigTIFF; BMP-Trailing-Metadaten werden abgeschnitten und das Dateigrößen-Feld neu geschrieben - EPUB: stdlib-only Container-Bereinigung — OPF-Metadaten und XHTML-Meta/JSON-LD gescrubbt, eingebettete Raster-/SVG-Medien entfernt, Layer A auf XHTML-Body-Text angewendet, marker-tragende Metadaten-Teile verworfen und OCF-verschlüsselte Teile unangetastet durchgereicht
- Dateinamen-Bereinigung: HTTP-Service verweigert unsichere, vom Client gelieferte Ausgabenamen
- Markdown-Frontmatter-Cleaner repariert, der bei verschachtelten AI-Keys abstürzte und sie leaken ließ (#25)
- Text-Tools verweigern Binär-Input;
--force-textüberschreibt (#24) --jsonunterdrückt nicht mehr den Exit-Code für Residual-Signal (#30)inspect_filegibt den Dateinamen aus (#50)- Mixed-Case-CMS-Generator-Meta-Tags beibehalten (#42)
- Load-bearing Script-Invisibles beibehalten, PUA entfernen in Layer A (#38, #52)
- Script-Joiner, Flaggen-Emoji und arabische Cf-Marks beibehalten in Layer A (#28)
- Website-Audit gegen SSRF und Gzip-Bomben gehärtet (#49)
- SECURITY.md verweist nur auf den privaten Advisories-Kanal (#51)
- Windows: PowerShell-Ports der Setup-Bootstraps (#40)
- Docs: Stars/Forks-Shields hinzugefügt und Star-History-Chart entfernt; MarkLLM zu README-Referenzen hinzugefügt; Pull-Request-Template; Plan für Docker CLI + API-Deployment
v0.4.0 — Pixel-Entfernung, Finding-Confidence, Windows- & False-Positive-Fixes
Optionale CtrlRegen-Pixel-Entfernung (externes Backend)
- Optionale Pixel-Domain-Wasserzeichen-Entfernung über einen externen
mertizci/noai-watermark-Checkout:clean_ctrlregen.py-Adapter +setup_ctrlregen.sh-Bootstrap (gepinnter Commit, Sparse-Checkout, venv, SHA-Verifikation), plusDockerfile.ctrlregenundmake bootstrap-ctrlregen/docker-ctrlregen-build/smoke-ctrlregen clean_image.py --remove-pixel ctrlregenführt Metadaten-Strip → CtrlRegen-Entfernung → optionalen Reverse-SynthID-Vorher/Nachher-Score aus;inspect_image.pyweist bei hohem SynthID-Score auf das Flag hin- Konservative Standard-Intensität
0.25(Presets 0.15/0.25/0.35/0.5/0.7); die 512×512-native Pipeline wird vom Backend für größere Bilder automatisch gekachelt; der torch-Subprozess erhält höhere, per Env überschreibbare Ressourcen-Limits - Backend wird nie gebündelt:
noai-watermarkliefert keine LICENSE-Datei mit (wird als all-rights-reserved behandelt), und seine Auto-Install-/Restart-Codepfade werden umgangen, indemCtrlRegenEnginedirekt verwendet wird
Finding-Confidence und aggregierte Audits
- Findings werden jetzt als
confirmed/probable/informational/likely_false_positiveklassifiziert und in Text-/Image-/Container-JSON sowie menschlichen Reports ausgegeben - Neue
audit_dir.py(rekursiver Baum) undaudit_website.py(Sitemap-Discovery + Crawl) aggregieren Reports; dokumentiert in SKILL.md
False-Positive-Fixes
- DOCX: nur
docProps/customXmlscannen, nicht den sichtbaren Body (#14) - Text Layer A: Emoji
VS16/ZWJnach einer Emoji-Basis beibehalten; neues paranoisches Flag--strip-emoji-glue(#22) - HTML: CMS-Generator-Tags als informativ behandeln, nicht als AI-Metadaten (#13)
- PDF: Stream-Payloads vom AI-Marker-Byte-Scan ausschließen (#13)
- Inspect-Reports vermerken nicht unterstützte/Best-Effort-Pfade
Windows-Unterstützung
- POSIX-only
preexec_fnundos.fchmodgaten, damit Schreibvorgänge und optionale Tools unter Windows laufen (#15, #23) - stdio auf UTF-8 umkonfigurieren, damit umgeleitete Windows-Streams bei unsichtbarem Unicode nicht mehr werfen; Windows-CI-Leg + CLI-Smoke-Run (#23)
Docs und Supply Chain
- README-CtrlRegen-Abschnitt + Research-Referenzen (CtrlRegen, UnMarker, Forensic-Stealth-Caveat), Responsible-Use-Disclaimer; SKILL/Matrix/Vendor-Notes/Ethics-Updates
- Dependabot-Konfiguration + CODEOWNERS für Security-Pfade; Bump von scipy/numpy/opencv-python/scikit-learn/pywavelets und des Basis-Images auf Python 3.14-slim
- Mock-basierte CtrlRegen-Tests (kein torch in CI)
v0.3.2 — Security-Härtung (sichere Writes, HTTP-Client, CI-Supply-Chain)
- Sichere, atomare Output-Writes: jeder Cleaner schreibt jetzt über Temp-Datei + atomares Rename (
safe_write_bytes/safe_write_text), verweigert symlinkte Ziele und erstellt.bak-Backups über denselben sicheren Pfad — vorab platzierte Symlinks (z. B. in/tmpoder Download-Verzeichnissen) können einen Clean-Write nicht mehr auf eine beliebige Datei umleiten rewrite_text.pyHTTP-Client-Härtung: Redirects werden grundsätzlich verweigert, sodass ein API-Key imAuthorization-Header niemals an einen unvalidierten Host erneut gesendet werden kann; Nicht-Loopback-Endpunkte sind standardmäßig verweigert (Opt-in mit--allow-remoteoderWATERMARKS_REWRITE_ALLOW_REMOTE=1); nur http(s)-Schemes werden akzeptiert;--api-keywurde entfernt — Keys nur per Env überWATERMARKS_REWRITE_API_KEY- Ressourcen-Limits: Standard-Max-Input 1 GiB → 256 MiB, neues 64-MiB-stdin-Limit, DOCX/ODT-Zip-Budget 512 MiB → 128 MiB und
RLIMIT_AS/RLIMIT_FSIZEfür exiftool/c2patool/SynthID-Subprozesse (alle Limits per Env überschreibbar) - Supply Chain: CI-Actions SHA-gepinnt mit
permissions: contents: read, gepinnte Dev-Deps (requirements-dev.txt), einpip-audit-Schritt und ein neuer CodeQL-Workflow; das Docker-Image läuft jetzt als unprivilegierter User mit gepinntem pip - Scorer-Deps: Pillow von 10.4.0 → 12.3.0 gebumpt (24 bekannte CVEs); API-Nutzung gegen den gepinnten Upstream-Commit verifiziert
- Tests: 18 neue Security-Regressionstests (60 insgesamt, alle bestehen)
v0.3.1 — stärkerer Layer-B-Statistik-Wasserzeichen-Rewrite
- Der Standard-Paraphrase-Modus von
rewrite_text.pyführt jetzt einen expliziten Wortwahl- + Syntax-Angriff durch (Satzteil-Reihenfolge, Konnektoren, Übergangswörter, Satzgrenzen, Funktionswörter) statt eines generischen Rewrites - Neues
--tactic humanize: Zero-Shot-„schreibe wie ein Mensch“-Durchlauf, der auf formelhafte AI-Phrasierung abzielt - Neues
--tactic code: schreibt Kommentare, Docstrings und String-Literale um und benennt lokale Identifier um, während Verhalten und öffentliche API-Namen erhalten bleiben - Struktureller Durchlauf gibt jetzt „natürliche, variierte menschliche Prosa“ aus statt AI-typischem „klarem professionellem Stil“
- Neues
--temperature(Standard0.9) für Ollama- und OpenAI-kompatible Backends - Neues
--candidates N: generiert N Rewrites und wählt die lexikalisch am stärksten divergierende (Bigram-Jaccard-Distanz) mit Length-Drift-Guard - Stärkere Modell-Hygiene: lokale Open-Weight-Modelle bevorzugen und jeden bekannten wasserzeichenbehafteten Vendor vermeiden, nicht nur den vermuteten Ursprung
- Residual-Risk-Reporting unterscheidet jetzt kurzen/höchst vorhersehbaren Text (geringeres Risiko) von langer, hochentropischer Prosa (höheres Risiko)
- Docs aktualisiert in
SKILL.md,removal-matrix.mdundvendor-notes.md; Tests decken neue Prompts, Divergenz-Scoring und Kandidatenauswahl ab
v0.3.0 — optionales SynthID-Pixel-Scoring
- Optionaler Pixel-Domain-SynthID-Scorer über einen externen
aloshdenny/reverse-SynthID-Checkout (score_synthid.py); verfügbar ininspect_image.py/clean_image.pymitREVERSE_SYNTHID_DIRoder--synthid-dir setup_synthid.sh-Bootstrap (nur Scorer-Dependencies;--fullinstalliert Upstream-Requirements);Dockerfile.synthidplusmake docker-synthid-build/docker-synthid-help- Makefile-Ziele
smoke-synthidundbootstrap-synthid - Tests für den Scorer-Adapter, CLI-Unavailable-Pfad, JSON-Parsing und Laufzeitfehler
- Docs: nur Detection/Scoring (keine Pixel-Entfernung); Upstream-Code wird nicht gebündelt und bleibt unter seiner nicht-kommerziellen Research License
v0.2.0 — c2patool-False-Positive-Fix
image_meta.py:has_manifestmarkiertError: No claim found/No JUMBF data foundnicht mehr als Manifest (Operator-Precedence-Bug: die negativen Marker vetoieren jetzt jeden positiven Zweig)- Neue
tests/test_c2patool_report.py(4 Fälle: kein Claim, kein JUMBF, echtes Manifest, Tool fehlt) - Docs:
c2patool-Links korrigiert (Repo verschoben nachcontentauth/c2pa-rs); Disclaimer zu den Qualitätskosten der Text-Wasserzeichen-Entfernung hinzugefügt
v0.1.0 — Packaging-Politur + Provenance-Ehrlichkeit
Makefile(test/smoke/install-skill) undpytest.ini- Fixture-Samples für Markdown, HTML, SVG; PDF-Degraded-Clean-Test
- Docs: Branchen-Zwei-Schichten-Modell (hart gebundenes C2PA vs. Soft Binding / SynthID-Medien)
- README-Residual-Risk-Tabelle + Links zu externen Verify-Tools
- Referenz: Institute of AI PM C2PA/SynthID-Guide
- Soft-Binding- und Pixel-/Audio-/Video-Wasserzeichen explizit außerhalb des Scope in Skill/Matrix/Ethics
v0.0.1 — initiales Multi-Vendor-Release
- Agent-Skill
remove-ai-marks(ersetzt das Claude-onlyremove-claude-marks) - Layer A: unsichtbare Unicode- / Bidi- / Tag-Zeichen / Space-Homoglyphen (
inspect_text/clean_text) - Layer B: Rewrite-Guidance + optionales
rewrite_text.py(Print-Prompt, Ollama, OpenAI-kompatibel) - Dateien: C2PA/AI-Metadaten-Strip für PNG, JPEG, SVG, PDF, DOCX, ODT, HTML, Markdown
- Vereinheitlichte
inspect_file.py/clean_file.py - Multi-Vendor-Docs (Claude, Gemini/SynthID-Klasse, OpenAI, Open-LLM)
- Stdlib-first-Skripte; optional
c2patool/exiftool
License
MIT — siehe LICENSE.
Bibliography
- How Claude marks AI-generated content (Anthropic)
- Dathathri et al., Scalable watermarking for identifying large language model outputs (SynthID-Text, Nature 2024)
- Google AI for Developers, SynthID safeguards (Gemini API docs)
- C2PA / c2patool
- Kirchenbauer et al., A Watermark for Large Language Models
- Evseev, D. (Arbitration City), Accurate, Costless, and Invisible AI Text Watermarking for Self-Hosted AI Inference (technischer Report, August 2026) — Keyed-Gumbel-Watermarking, ausgeliefert in der Open-Source-Engine arbi-serve, mit Exact-Test-Detection und Speculative-Decoding-Unterstützung — PDF
- THU-BPM/MarkLLM (vereinheitlichtes Toolkit zur Evaluierung von LLM-Watermarking-Algorithmen)
- Pan et al., MarkDiffusion: An Open-Source Toolkit for Generative Watermarking of Latent Diffusion Models (JMLR) — das Embedding-Toolkit, das die optionale Image-Watermark-Harness dieses Repos umschließt — Code, Docs
- Zhang et al., Watermarks in the Sand: Impossibility of Strong Watermarking for Generative Models (ICML 2024)
- Sander et al., Watermarking Makes Language Models Radioactive — Wasserzeichen überleben Fine-Tuning und markieren nachgelagerte Modelle, die auf wasserzeichenbehafteten Daten trainiert wurden
- Pan et al., Can LLM Watermarks Robustly Prevent Unauthorized Knowledge Distillation? — wasserzeichenbasierte Provenance und Schutz gegen Knowledge Distillation
- google-deepmind/synthid-text (Research-Referenz; hier nicht zur Detection verwendet)
- aloshdenny/reverse-SynthID (Research-Referenz)
- ETH Zurich SRI, Probing SynthID (Research-Blog zur Erkennbarkeit von SynthID-Wasserzeichen)
- Liu et al., Image Watermarks are Removable Using Controllable Regeneration from Clean Noise (ICLR 2025) — die Pixel-Regenerations-Methode, die das optionale CtrlRegen-Backend implementiert — Code
- Kassis & Hengartner, UnMarker: A Universal Attack on Defensive Image Watermarking (arXiv:2405.08363; IEEE S&P 2025) — ein universeller Wasserzeichen-Angriff, verglichen auf einer anderen Metrik als CtrlRegen
- Goonatilake & Ateniese, Removing the Watermark Is Not Enough: Forensic Stealth in Generative-AI Watermark Removal (arXiv:2605.09203) — motiviert den konservativen Intensitäts-Standard: Entfernung kann dennoch forensische Spuren hinterlassen
- mertizci/noai-watermark (CLI/Python-Toolkit für SynthID/StableSignature/TreeRing-Entfernung und AI-Metadaten-Stripping)
- 0xROOTPLS/DeSynth (SynthID-Entfernung für OpenAI/Google-Bilder)
- Institute of AI PM, AI Content Provenance and Watermarking: The PM's Guide to C2PA and SynthID (Zwei-Schichten-Branchenmodell: C2PA + imperzeptibles Wasserzeichen / Soft Binding; SB 942 / EU AI Act Art. 50-Kontext)