
Eine Privacy-First-App, die KI-Wasserzeichen aus Inhalten entfernt, die dir gehören.
_ _ _ ____ ___ ____ ____ _ _ ____ ____ _ _ ____ ____ ____ _ _ ____ _ _ ____ ____
| | | |__| | |___ |__/ |\/| |__| |__/ |_/ [__ __ |__/ |___ |\/| | | | | |___ |__/
|_|_| | | | |___ | \ | | | | | \ | \_ ___] | \ |___ | | |__| \/ |___ | \
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.
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)
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.
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-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
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
mkdir -p .grok/skills ln -sfn "$(pwd)/skills/remove-ai-marks" .grok/skills/remove-ai-marks
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
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):
Core-Skripte benötigen nur die Python 3.10+-Standardbibliothek. Layer-B-Modellaufrufe sind optional.
SCRIPTS=service/scripts
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
python3 "$SCRIPTS/inspect_text.py" draft.md python3 "$SCRIPTS/clean_text.py" draft.md -o draft.cleaned.md --stats
python3 "$SCRIPTS/rewrite_text.py" draft.md --backend print-prompt --tactic paraphrase
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.
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).
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:
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.
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.
make docker-synthid-build
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.
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.
SCRIPTS=service/scripts
"$SCRIPTS/setup_ctrlregen.sh"
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).
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:
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.
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.
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:
--offline beim 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 (transformers trust_remote_code ist
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.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.
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:
DiffusionPurification-Regenerations-
Angriff wird als clean_image.py --remove-pixel diffusion bereitgestellt, 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.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
"$SCRIPTS/setup_markdiffusion.sh"
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
MARKDIFFUSION_DIR=~/markdiffusion
~/markdiffusion/.venv/bin/python "$SCRIPTS/markdiffusion_harness.py" purify
wm.png -o wm.purified.png --purification-intensity 0.3 --json
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.
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
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:
pdfwrite mit 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. never ist die Option für ein Dokument, dessen Streams unangetastet überleben müssen.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.
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):
Branchenkontext mit zwei Ebenen (C2PA + imperzeptibles Wasserzeichen): Institute of AI PM guide.
Von Anbietern bereitgestellte Prüfer, um zu verifizieren, ob Inhalte KI-Herkunftsmarkierungen tragen:
Matrix: skills/remove-ai-marks/references/removal-matrix.md.
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.
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 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 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 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.
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.
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
repos:
`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
/clean Layer-B-Neufassung, Wasserzeichen-Stealing-Modul, Audio-/Video-Wasserzeichen-Entfernung und breitere Benchmark-/Tooling-Abdeckungv0.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
/clean führt die Layer-B-Neufassung für Text nach Layer A aus. Der Standard kommt aus config/clean_strategy.json; ein per-Request options.strategy überschreibt ihn, und /clean lehnt mit 400 ab, wenn das erforderliche Backend nicht konfiguriert ist (#315). Konfigurationspriorität: --strategy-config > WATERMARKS_CLEAN_STRATEGY_FILE > config/clean_strategy.json.mlm-Rewrite-Taktik: Maskierung eines Bruchteils der Inhaltswörter und Auffüllen mit roberta-large — eine nicht-autoregressive lokale Bearbeitung, sodass die Ausgabe den ursprünglichen Token-Stream mit Masked-LM-Vorhersagen vermischt (#311).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). erhielt einen -CLI-Pfad.Benchmark
Wasserzeichen-Stealing
Audio / Video / Bild
uuid-Box auf MP4/MOV/AVIF/HEIC erkannt (#264).zTXt/iTXt auf 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); /inspect legt explizite Evidenzklassen in der verdächtigen Payload offen (#277); Zeitstempel in HTTP-Request-Logs (#256); Thread-Payload-Bytes in HTTP-SynthID-Scoring und inspect_* einfließen lassen, um ein redundantes Read-back zu vermeiden.clean_file.py erhielt -q/--quiet/--only-changed (#254).Skills, Plugin & Hooks
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.py scannt Quell-, Doku- und i18n-Dateien, die der Router übersehen hat (#284); scannt .ts/.tsx/.jsx/.gd und 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
CI, Tooling & Doku
Format- & Container-Abdeckung
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)docProps-Provenance-Felder immer leeren; hängende Relationships nach customXml-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)Layer A (unsichtbares Unicode) Härtung
Default_Ignorable-Codepoints ohne legitime Interchange-Nutzung (U+2065, U+FFF0–U+FFF8, U+E0000, U+E0080–U+E00FF, U+E01F0–U+E0FFF — gemeldet als reserved_ignorable), der 66 Noncharacters (U+FDD0–U+FDEF plus U+FFFE/U+FFFF pro Plane — gemeldet als noncharacter) und drei blank-rendernde Default_Ignorable-Träger, die der -Catch-all nie sah (, , ). 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 angewendetLayer-B-Neufassung & Wasserzeichen-Erkennung
--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-stats meldet nun evaluator / max_loops / attempts_made / passed und pro Versuch candidate_scores (#153)detect_gumbel.py implementiert den modellfreien Replay-Test (u = PRF(Hash(key, window), token); exakter Gamma-Tail-p-Wert; Repeated-Window-Masking) ohne GPU, Modell oder Logits. (env , bevorzugt) macht es zum Iterative-Loop-Evaluator (Priorität: gumbel > markllm > lexikalische Divergenz) und es wird als in und exponiert. Nur Same-Key — kein Vendor-Oracle; der Key wird nie geloggt (#190)Verteilung: Plugin-, Hook- und Skill-Installationen
.claude-plugin/plugin.json + marketplace.json), sodass beide Skills mit /plugin marketplace add guillaumemeyer/watermarks-remover und dann /plugin install watermarks-remover@watermarks-remover installiert und in-place aktualisiert werden. make plugin-validate führt claude plugin validate . --strict aus; tests/test_plugin_manifest.py prüft die Manifeste ohne die CLIinstall_skill.py erhielt ein --target (claude-code, claude-project, cowork, cursor) und einen -Selektor, der beide mitgelieferten Skills abdeckt, plus , und . Das -Target baut ein reproduzierbares Upload-Bundle (, einzelnes Top-Level-Skill-Verzeichnis); jedes Target validiert gegen die Agent-Skills-Packaging-Regeln und das 30-MB-Upload-Limit. Neue -Targets: , , , , HTTP-Dienst
POST /clean/batch, /inspect/batch (#137) und POST /detect/batch (#151)/clean erhalten und sichere Schreibvorgänge in av_meta verwenden (#150); portables base64 im /detect-curl-Beispiel verwenden (und die macOS-realpath-Portabilität in den Bootstraps beheben, #185)Audit / Inspektion & Sicherheit
audit_dir.py erhielt Multi-Worker-Concurrency und SARIF-2.1.0-Export (#101, #102)Zuverlässigkeits- & Korrektheits-Fixes
--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_usable beheben und verhindern, dass Kindprozesse unter Windows ein Konsolenfenster öffnenbench-synthid-text beheben; Flag-Passthrough für die Ghostscript-Probe vereinfachen und clean_text unnötiges noqa (Lint)CI / Tooling / Doku
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)<AppVersion> in docProps/app.xml wä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)Service- / Docker-Verteilung
skills/remove-ai-marks/) ist jetzt ein codefreier Remote-Client über HTTP; die gesamte Implementierung wurde nach service/scripts/ verschoben und läuft hinter server.py, einem stdlib-HTTP-Einstiegspunkt (/health, /inspect, /clean, /capabilities)service/scripts/server.py exponiert die Bereinigungspipeline über JSON/base64; die Härtung spiegelt die CLIs (Größenobergrenzen, Binär-Guard, atomare Schreibvorgänge, Loopback-Standard, optionale WATERMARKS_SERVER_API_KEY-Bearer-Auth)GET /openapi.json liefert 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 mit openapi-spec-validatorMarkDiffusion-Bild-Wasserzeichen-Harness (optional)
THU-BPM/MarkDiffusion, Apache-2.0): markdiffusion_harness.py mit watermark- / 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 diffusion führt den MarkDiffusion-DiffusionPurification-Regenerationsangriff als alternative Pixel-Entfernungs-Engine aus (konservative Intensität 0.3 Standard)setup_markdiffusion.sh-Bootstrap (PyPI-Pin 1.0.2; --checkout editierbarer Clone beim gepinnten Commit) + requirements-markdiffusion.txt + Dockerfile.markdiffusion und Makefile bootstrap-markdiffusion / / / MarkLLM-Text-Wasserzeichen-Harness (optional)
THU-BPM/MarkLLM-Checkout, Apache-2.0): detect_text_watermark.py mit detect- / watermark-Subcommands für KGW- und SynthID-Schematarewrite_text.py --markllm-scheme führt Before/After-Detektion um eine Layer-B-Neufassung herum aus und Per-Candidate-Detektion, wenn --candidates N>1 (env-gated; meldet cleared)setup_markllm.sh-Bootstrap + requirements-markllm.txt (gepinnte Deps) + Dockerfile.markllm und Makefile bootstrap-markllm / smoke-markllm / docker-markllm-build / 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)
requirements-markllm.txt pinnte tokenizers==0.23.1, was mit transformers==5.15.0 kollidiert (begrenzt auf tokenizers<=0.23.0; es existiert kein 0.23.0-Release) — jetzt auf tokenizers==0.22.2 gepinnt; torch auf den CPU-Wheel-Index verschoben (torch==2.13.0.*), damit das Image wie Dockerfile.markdiffusion nur CPU nutztsafetensors==0.4.3, transformers==4.37.2 → tokenizers<0.19) liefern keine Python-3.14-Wheels, daher ist das Basis-Image jetzt python:3.11-slim (digest-gepinnt, Multi-Arch)Dockerfile.markllm und kopierten nie nach (vorbestehender Bug) — hinzugefügtOptionale CtrlRegen-Pixel-Entfernung (externes Backend)
mertizci/noai-watermark-Checkout: clean_ctrlregen.py-Adapter + setup_ctrlregen.sh-Bootstrap (gepinnter Commit, Sparse-Checkout, venv, SHA-Verifikation), plus Dockerfile.ctrlregen und make bootstrap-ctrlregen / docker-ctrlregen-build / smoke-ctrlregenclean_image.py --remove-pixel ctrlregen führt Metadaten-Strip → CtrlRegen-Entfernung → optionalen Reverse-SynthID-Vorher/Nachher-Score aus; inspect_image.py weist bei hohem SynthID-Score auf das Flag hin0.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-Limitsnoai-watermark liefert keine LICENSE-Datei mit (wird als all-rights-reserved behandelt), und seine Auto-Install-/Restart-Codepfade werden umgangen, indem direkt verwendet wirdFinding-Confidence und aggregierte Audits
confirmed / probable / informational / likely_false_positive klassifiziert und in Text-/Image-/Container-JSON sowie menschlichen Reports ausgegebenaudit_dir.py (rekursiver Baum) und audit_website.py (Sitemap-Discovery + Crawl) aggregieren Reports; dokumentiert in SKILL.mdFalse-Positive-Fixes
docProps/customXml scannen, nicht den sichtbaren Body (#14)VS16/ZWJ nach einer Emoji-Basis beibehalten; neues paranoisches Flag --strip-emoji-glue (#22)Windows-Unterstützung
preexec_fn und os.fchmod gaten, damit Schreibvorgänge und optionale Tools unter Windows laufen (#15, #23)Docs und Supply Chain
safe_write_bytes / safe_write_text), verweigert symlinkte Ziele und erstellt .bak-Backups über denselben sicheren Pfad — vorab platzierte Symlinks (z. B. in /tmp oder Download-Verzeichnissen) können einen Clean-Write nicht mehr auf eine beliebige Datei umleitenrewrite_text.py HTTP-Client-Härtung: Redirects werden grundsätzlich verweigert, sodass ein API-Key im Authorization-Header niemals an einen unvalidierten Host erneut gesendet werden kann; Nicht-Loopback-Endpunkte sind standardmäßig verweigert (Opt-in mit --allow-remote oder WATERMARKS_REWRITE_ALLOW_REMOTE=1); nur http(s)-Schemes werden akzeptiert; --api-key wurde entfernt — Keys nur per Env über WATERMARKS_REWRITE_API_KEYRLIMIT_AS/ für exiftool/c2patool/SynthID-Subprozesse (alle Limits per Env überschreibbar)rewrite_text.py führt jetzt einen expliziten Wortwahl- + Syntax-Angriff durch (Satzteil-Reihenfolge, Konnektoren, Übergangswörter, Satzgrenzen, Funktionswörter) statt eines generischen Rewrites--tactic humanize: Zero-Shot-„schreibe wie ein Mensch“-Durchlauf, der auf formelhafte AI-Phrasierung abzielt--tactic code: schreibt Kommentare, Docstrings und String-Literale um und benennt lokale Identifier um, während Verhalten und öffentliche API-Namen erhalten bleiben--temperature (Standard 0.9) für Ollama- und OpenAI-kompatible Backends--candidates N: generiert N Rewrites und wählt die lexikalisch am stärksten divergierende (Bigram-Jaccard-Distanz) mit Length-Drift-GuardSKILL.md, removal-matrix.md und vendor-notes.md; Tests decken neue Prompts, Divergenz-Scoring und Kandidatenauswahl abaloshdenny/reverse-SynthID-Checkout (score_synthid.py); verfügbar in inspect_image.py / clean_image.py mit REVERSE_SYNTHID_DIR oder --synthid-dirsetup_synthid.sh-Bootstrap (nur Scorer-Dependencies; --full installiert Upstream-Requirements); Dockerfile.synthid plus make docker-synthid-build / docker-synthid-helpsmoke-synthid und bootstrap-synthidimage_meta.py: has_manifest markiert Error: No claim found / No JUMBF data found nicht mehr als Manifest (Operator-Precedence-Bug: die negativen Marker vetoieren jetzt jeden positiven Zweig)tests/test_c2patool_report.py (4 Fälle: kein Claim, kein JUMBF, echtes Manifest, Tool fehlt)c2patool-Links korrigiert (Repo verschoben nach contentauth/c2pa-rs); Disclaimer zu den Qualitätskosten der Text-Wasserzeichen-Entfernung hinzugefügtMakefile (test / smoke / install-skill) und pytest.iniremove-ai-marks (ersetzt das Claude-only remove-claude-marks)inspect_text / clean_text)rewrite_text.py (Print-Prompt, Ollama, OpenAI-kompatibel)inspect_file.py / clean_file.pyc2patool / exiftoolMIT — siehe LICENSE.
| 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 |
| 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) |
| 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", ...}}, ...]} |
| 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 |
| 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 |
rewrite_text.py--strategyCfU+180FU+3164U+FFA0U+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 überallrewrite_text.py --gumbel-keyWATERMARKS_GUMBEL_KEYgumbel/capabilities/detectparaphrase:3; Report und CSV tragen Versuche pro Dokument (mean_attempts / att, attempts / evaluator / passed-Spalten); --rewrite-loops spiegelt --max-loops--skill--list--linkCLAUDE_CONFIG_DIRcoworkdist/<skill>.zipmakeinstall-claude-code-skillinstall-claude-code-text-skillinstall-claude-project-skillpackage-cowork-skillpackage-cowork-text-skillPostToolUse-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; clean entfernt 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 der hook_mode-Einstellung des Plugins oder WATERMARKS_HOOK_MODE; die Erkennung verwendet audit_lib.scan_file / is_actionable wieder, 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 bleibtclean-user-facing-text nennt Cursor nicht mehr als einzigen Hostservice/Dockerfilecompose.yaml bringt die gesamte Infrastruktur hoch (core immer; markllm / markdiffusion hinter profile: harness; ctrlregen / synthid hinter profile: heavy als Local-Only-Builds); Dienste sind mit wr- präfixiert; Harness-/Heavy-Dienste verwenden standardmäßig command: ["--help"], sodass docker compose up --profile harness --profile heavy sauber beendet wird (One-Shot-CLIs werden mit docker compose run ausgeführt); neues make compose-check / compose-check.sh validiert den laufenden Stack (nur Exit-Code).github/workflows/release-images.yml veröffentlicht core-, markllm-, markdiffusion-Images bei v*-Tags; ctrlregen / synthid werden nie veröffentlicht (Upstream-Lizenzierung).env.example + Service-Konfigurationsanleitung; docker compose lädt .env automatisch; .env ist gitignored (Deny-by-Default).gitignore und service/.dockerignore sind jetzt Deny-by-Default — nur explizit erlaubte Pfade können committet oder in einen Build-Kontext gesendet werden (Image-Kontexte liefern nur service/scripts/, was alles ist, was die Dockerfiles COPYen)tests/test_http_server.py (13 Fälle) für den HTTP-Dienst; alle Suiten auf service/scripts/ umgestelltsmoke-markdiffusiondocker-markdiffusion-builddocker-markdiffusion-helptests/test_markdiffusion_harness.py) — kein torch in CI; references/markdiffusion.md-Referenzdokumentremoval-matrix.md, markdiffusion.mddocker-markllm-help--offline Cache-Only-Modell-Loading (kein HF-Egress, kein Remote-Code), 1-MiB-Config-Cap, optionales WATERMARKS_MARKLLM_RLIMIT_AS auf dem Rewrite-Subprozess, gepinntes torch im Dockerfile und Clone-SHA-Verifikation in Dockerfile.markllmtests/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.mdDockerfile.markdiffusioncommon.py/appC2PA-, XMP-, EXIF- und ICC-Profil-Chunks (#37)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--force-text überschreibt (#24)--json unterdrückt nicht mehr den Exit-Code für Residual-Signal (#30)inspect_file gibt den Dateinamen aus (#50)CtrlRegenEngineRLIMIT_FSIZEpermissions: contents: read, gepinnte Dev-Deps (requirements-dev.txt), ein pip-audit-Schritt und ein neuer CodeQL-Workflow; das Docker-Image läuft jetzt als unprivilegierter User mit gepinntem pip