mirror of
https://github.com/guillaumemeyer/watermarks-remover.git
synced 2026-08-22 13:11:57 +02:00
* feat: optional MarkLLM text-watermark verification harness Add an optional external backend wrapping THU-BPM/MarkLLM (Apache-2.0) so a specific statistical text-watermark scheme (KGW / SynthID-Text) can be verified before/after a Layer B rewrite. - detect_text_watermark.py: detect/watermark subcommands, external checkout at a pinned commit, exit codes 0/1/2/3, --json - rewrite_text.py --markllm-scheme: before/after detection around the rewrite, reports a `cleared` flag; never fails the rewrite when the backend is unavailable - setup_markllm.sh + requirements-markllm.txt (pinned deps) + Dockerfile.markllm + Makefile bootstrap/smoke/docker targets - tests/test_markllm_detect.py: 16 mock-based cases (no torch in CI) - Docs: verification-harness caveat (same-config-only, not a vendor-detector oracle) in README, SKILL.md, removal-matrix, vendor-notes * chore: tidy merged Unreleased changelog list * security: harden the MarkLLM harness (offline, caps, supply-chain) Addresses the PR security review: - detect_text_watermark.py: --offline loads the scoring model from the HF cache only (local_files_only + HF_HUB_OFFLINE, no remote code), and the algorithm config is capped at 1 MiB so a crafted huge file is refused before either this script or upstream reads it into memory - rewrite_text.py: WATERMARKS_MARKLLM_RLIMIT_AS (env, POSIX) optionally applies RLIMIT_AS to the MarkLLM subprocess; off by default because torch/CUDA needs large address spaces - Dockerfile.markllm: drop the unpinned torch install (it is pinned in requirements-markllm.txt) and verify the cloned upstream commit SHA - tests: offline flag, config-too-large, and preexec/rlimit cases - docs: hardening knobs in README + SKILL.md; changelog updated
3.5 KiB
3.5 KiB
Removal matrix
| Target | Method | Script / action | Side effects | Verifiable today? |
|---|---|---|---|---|
| Invisible Unicode / exotic spaces / bidi / tags | Strip / normalize | inspect_text.py, clean_text.py, clean_file.py |
Minimal | Yes (codepoint report) |
| Statistical text watermark (SynthID-class / Kirchenbauer) | Multi-pass paraphrase / humanize / back-translate / structural | Agent Layer B + optional rewrite_text.py |
Meaning/style drift | No without vendor key/detector; MarkLLM harness (detect_text_watermark.py) verifies a specific scheme config before/after |
| C2PA on PNG/JPEG/WebP | Drop APP11 / PNG caBX / RIFF C2PA / exiftool |
clean_image.py |
Loses provenance metadata | Yes |
| SVG metadata / XMP | Drop <metadata>, xmpmeta |
clean_file.py |
Loses SVG metadata | Yes (re-inspect) |
| PDF XMP / info | exiftool -all= preferred |
clean_file.py |
Loses PDF metadata; degraded without exiftool | Partial |
| DOCX props / customXml | Rewrite OOXML zip | clean_file.py |
Loses doc properties | Yes |
| ODT meta:generator | Scrub meta.xml |
clean_file.py |
Loses generator tag | Yes |
| HTML generator / JSON-LD provenance | Strip tags | clean_file.py |
Loses meta | Yes |
| Markdown AI frontmatter keys | Drop keys | clean_file.py |
Loses YAML keys | Yes |
| Pixel image watermark (SynthID-media / StegaStamp / Tree-Ring / StableSignature) | CtrlRegen regeneration (external backend) | clean_ctrlregen.py / clean_image.py --remove-pixel ctrlregen |
Regenerates pixels; heavy compute; detail drift at higher strength | No without official detector; reverse-SynthID score is a local surrogate |
| Audio / video watermarks (SynthID-media) | — | Out of scope | — | — |
| C2PA soft binding (in-content link to manifest) | — | Out of scope (survives our metadata strip) | — | Vendor detector only |
| Data-driven model backdoors | — | Out of scope | — | — |
Default pipeline
- Inspect (
inspect_file.pyor specific inspect_*). - Deterministic clean — Layer A text and/or container/image metadata; for images, optionally add pixel removal (
--remove-pixel ctrlregen) after the metadata strip. - Always offer Layer B rewrite for prose (paraphrase → optional strong pass:
humanize/ back-translate / structural). - Prefer a non-origin, open-weight rewrite model when available (avoid re-stamping).
- Layer A again after rewrite.
- Report: Layer B is best-effort; residual risk remains.
- Optional verification:
rewrite_text.py --markllm-scheme kgw|synthidruns a MarkLLM before/after detection (externaldetect_text_watermark.pyharness) to show a specific scheme config clears. Same-config-only; not a vendor-detector oracle.
Code vs prose
- Prose / Markdown / HTML body: full A + B.
- Code: Layer A + formatter; statistical marks are weak; offer
coderewrite (comments/docstrings/string-literal wording + local identifier renames) with user OK.
Layer B strengths
| Strength | When |
|---|---|
paraphrase |
Default; explicit word-choice + syntax churn |
humanize |
Zero-shot "write like a human" token reshuffle |
backtranslate |
Stronger token reshuffle via pivot language |
structural |
Strongest; most drift (outline → human prose) |
code |
Comments/docstrings/string-literal wording + local identifier renames |
Frontier production watermarks are currently token-by-token (streaming constraint); paragraph-level robust methods (SemStamp / PostMark) are not yet deployed, so paraphrase-class attacks remain effective today.