ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOPMulti-Platform Sync

The Developer Publishing Hub. Write once, publish everywhere, and make your work citation-ready with built-in SEO, AEO, and GEO discovery support. Zero reader paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Developer API & CLI
  • Write for Us
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

© 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls · Full content ownership
All systems operational
Homewatermarks-remover: Architecture Review & Getting-Started Guide

watermarks-remover: Architecture Review & Getting-Started Guide

A technical deep dive into watermark detection, metadata stripping, agent integration, optional backends, security, and the limits of “AI watermark removal.”

Tomson Alex
Tomson Alex
Blogger
September 4, 2026
9 min read
watermarks-remover: Architecture Review & Getting-Started Guide
#AI Watermarks#Software Architecture#AI Provenance#privacy#developer tools
👍1

Repo: guillaumemeyer/watermarks-remover License: MIT (core project) · Latest tagged release: v0.6.0 One-liner: An agent skill plus a stdlib-Python HTTP service that strips AI-provenance marks (invisible Unicode, statistical text watermarks, and file/container metadata such as C2PA/EXIF/XMP) from content the user owns.


1. What it actually is

Under the marketing framing ("privacy-first," "AI provenance"), this is a watermark/metadata-stripping toolkit with three cooperating pieces:

  1. A skill (markdown instructions for an LLM agent host — Claude Code, Cursor, Cowork, Grok) that tells the model when and how to invoke cleaning.

  2. A stdlib Python HTTP service that does the actual work — no third-party dependencies required for the core path.

  3. A set of optional, non-bundled external backends (CtrlRegen, a reverse-SynthID scorer, MarkLLM, MarkDiffusion) for pixel-domain removal and research-grade verification, each isolated behind its own venv/Docker image because of licensing or dependency conflicts.

The project is explicit that this only works against your own content, and it draws a hard line between what it can verify (deterministic Unicode/metadata removal) and what is best-effort (defeating statistical, sampling-based text watermarks).


2. Design philosophy: three layers, honestly labeled

The architecture is organized around a threat/marking taxonomy rather than around file types, which is the most interesting design decision in the project:

Layer

Target

Mechanism

Certainty

Layer A

Invisible Unicode (ZWSP, bidi controls, exotic spaces, tag characters, noncharacters)

Deterministic scripts

Verifiable — you can count what was removed

Layer B

Statistical/sampling-based text watermarks (Kirchenbauer-style green-list, SynthID-Text, keyed-Gumbel/Aaronson EXP)

LLM-driven rewrite, optionally detection-guided

Best-effort only — no universal detector exists

File/container

C2PA, EXIF, XMP, document properties across ~20 formats

Format-specific parsers/rewriters

Verifiable per-format, with documented gaps (embedded images inside PDFs, pixel-domain marks)

The README is unusually candid that Layer B is fundamentally different in kind from the other two: a statistical watermark is spread across every token choice, so removing it requires substantially rewording the text, which necessarily degrades voice and precision. The project frames this as an honest trade rather than hiding it — a design choice worth noting in a review, since most "watermark remover" tools don't disclose this.


3. System architecture

3.1 Skill/service split

This is the core architectural decision post-v0.5.0. The skill directory (skills/remove-ai-marks/) ships no implementation code — it's a thin HTTP client description that an agent model reads and acts on. All actual logic lives in service/scripts/, fronted by a stdlib server.py. This means:

  • The agent host (Claude Code, Cursor, etc.) needs no Python runtime or dependencies of its own.

  • The service can run locally (127.0.0.1:8765 by default, loopback-only) or remotely for cloud/Cowork sessions, with bearer-token auth via WATERMARKS_SERVER_API_KEY.

  • Multiple agent hosts share one service, avoiding duplicated cleaning logic.

3.2 The HTTP service surface

Method

Path

Purpose

GET

/health

Liveness + version

GET

/capabilities

Which optional tools/backends are actually usable (version-probed, not just present on PATH)

GET

/openapi.json

Spec generated from the live route table, so it can't drift

POST

/inspect, /inspect/batch

Report findings without modifying the file

POST

/detect, /detect/batch

Run watermark detectors (separate from cleaning; never calls vendor APIs unless asked)

POST

/clean, /clean/batch

Strip marks; batch endpoints isolate per-file failures instead of aborting

All payloads are base64-encoded JSON, and the service auto-detects file kind by extension + magic bytes rather than trusting the client's claim.

3.3 Distribution surfaces

The project supports an unusually wide range of install paths for what is fundamentally one Python codebase:

  • A generic installer (install_skill.py, stdlib-only) that targets Claude Code (personal/project), Cowork/claude.ai, and Cursor, validating output against the community Agent Skills packaging spec before writing anything.

  • A Claude Code plugin + single-plugin marketplace (.claude-plugin/) for two-command install/update.

  • A PostToolUse hook (hook_written_file.py) that runs deterministically on every file write regardless of whether the model decides to invoke the skill — this is the project's answer to the fact that skills are cooperative (model-invoked) while hooks are not.

  • A pre-commit hook wrapping the same CLIs, so marked files are caught before they're committed rather than only at write-time or in CI.

  • Docker / docker-compose, with the core image on GHCR and optional harness (MarkLLM, MarkDiffusion) and heavy (CtrlRegen, SynthID scorer) profiles for anything with heavyweight or licensing-encumbered dependencies.

Worth flagging architecturally: the README is explicit that no hook can intercept the assistant's own chat message before the user reads it — Claude Code's Stop hook only sees the message read-only, with no pre-send filter. So the deterministic guarantee only covers files written to disk (plus anything caught by the pre-commit gate); text that only ever exists in a chat transcript depends on the model choosing to invoke the skill, which is best-effort by construction. This is a real architectural boundary, not a bug.

3.4 Optional heavy backends — why they're kept separate

Four capabilities are deliberately not bundled into the core service, each for a different reason:

Backend

What it adds

Why it's external

CtrlRegen (mertizci/noai-watermark)

Pixel-domain removal (SynthID-class, StegaStamp, Tree-Ring, StableSignature) via ControlNet+DINOv2 regeneration

Upstream ships no LICENSE file (treated as all-rights-reserved) — never redistributed, only cloned at a pinned commit into a local venv/image

reverse-SynthID scorer

Confidence score for SynthID pixel watermarks

Upstream is under a non-commercial Research License

MarkLLM

Same-config watermark/detect harness for research verification

Apache-2.0, but adds torch/transformers — kept in its own venv

MarkDiffusion

Image-watermark generation/detection/purification harness

Same rationale — Apache-2.0 but heavy, and purification is a fallback with more content drift than CtrlRegen

This licensing-aware isolation — pinned commits, dedicated venvs, "local build only, never published" image tags — is a genuinely careful piece of engineering hygiene that's easy to overlook but matters a lot for a project redistributing Docker images.


4. Detection subsystem: a separate concern from cleaning

Detection is architected as its own pipeline, not a side effect of cleaning, and it's fail-soft everywhere: an unconfigured or errored detector reports {"available": false} rather than blocking a clean. Three text detectors exist:

  • MarkLLM — valid only against the same scheme configuration used at generation; explicitly documented as "not a vendor oracle."

  • Keyed-Gumbel replay (detect_gumbel.py) — a stdlib, model-free replay that recomputes the pseudo-random function from the text and tests the statistical tail. No GPU or model needed, but only valid for self-hosted engines where you hold the key.

  • A reserved seam for a Claude vendor detector — not yet implemented, sitting ready for when Anthropic ships a public detection API.

The detection-guided Layer B rewrite loop is the most sophisticated piece of the text pipeline: it generates candidate rewrites, evaluates each against whatever detector is configured (Gumbel > MarkLLM > lexical-divergence fallback), and stops as soon as one passes — with a documented --max-loops ceiling so it doesn't run indefinitely. When no detector is configured, it falls back to picking the most lexically diverged candidate, with no pass/fail claim at all.


5. File-format coverage

The container-cleaning layer covers roughly twenty formats (PNG/JPEG/WebP/AVIF/HEIC/BMP/GIF/TIFF/SVG, PDF, DOCX/XLSX/PPTX, EPUB/ODT, HTML/Markdown, and MP4/WAV/MP3/FLAC), each with format-specific logic for where provenance data actually hides. The PDF path is the most architecturally interesting because it needs three cascading passes, and the README's own justification for this is worth restating because it reveals a subtlety many tools miss:

  1. exiftool alone writes PDFs incrementally — it appends an update block and hides the /Info dictionary from the trailer, but the original bytes remain physically present in the file and are technically recoverable.

  2. qpdf --linearize re-serializes the document from its object graph, which actually discards those now-unreferenced objects.

  3. Neither pass touches an image inside the PDF (e.g., a scanned page), so a Ghostscript-driven deep_images pass is layered on top, with a lossless-first, re-encode-only-on-evidence policy — it only recompresses pixel data when APPn metadata is demonstrably still present after the document-level strip.

This "check whether the previous layer actually worked before escalating" pattern (verified by hashing pre/post streams) is a solid piece of defensive engineering.


6. Getting started

6.1 Fastest path: Claude Code plugin

/plugin marketplace add guillaumemeyer/watermarks-remover
/plugin install watermarks-remover@watermarks-remover

This installs both shipped skills (remove-ai-marks, service-backed; clean-user-facing-text, self-contained/text-only) and registers the write-time hook, with no local clone required.

6.2 Manual skill install (any supported host)

python3 install_skill.py --skill remove-ai-marks --target claude-code
# --target claude-project --project-dir PATH   for a per-project install
# --target cowork                              builds dist/<skill>.zip for claude.ai/Cowork upload
# --target cursor                              (default) symlinks/copies into ~/.cursor/skills

6.3 Start the service

make serve
# or directly:
python3 service/scripts/server.py --host 127.0.0.1 --port 8765

Requires only Python 3.10+ stdlib for the core path. Optional system tools (c2patool, exiftool, qpdf) are auto-detected and each unlock a specific capability tier — the service degrades gracefully (with an explicit warning) when any is missing.

6.4 Command-line quick use

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

Nothing above requires configuration — invisible-Unicode and file-metadata cleaning work out of the box. Layer B (the rewrite path) additionally needs a rewrite backend, configured via WATERMARKS_REWRITE_BACKEND (print-prompt by default — just prints the prompt, no model call — or ollama/openai-compatible).

6.5 Docker / compose, if you want the full stack

docker compose up -d                                     # core service only
docker compose --profile harness up -d                   # + MarkLLM / MarkDiffusion
docker compose --profile heavy up -d                      # + CtrlRegen / SynthID scorer (local builds)
make compose-check                                        # validates the running stack

6.6 Talking to the HTTP API directly

curl -s -X POST http://127.0.0.1:8765/clean \
  -H 'Content-Type: application/json' \
  -d "{\"file\": \"$(base64 < notes.md | tr -d '\n')\", \"name\": \"notes.md\"}"

7. Security & hardening posture

A few choices stand out as more careful than typical for a project this size:

  • Secrets never touch argv. API keys and the Gumbel replay key are read from environment variables only, and the docs repeatedly call this out (WATERMARKS_REWRITE_API_KEY, WATERMARKS_GUMBEL_KEY).

  • Remote calls require explicit opt-in. The rewrite hook defaults to loopback-only; sending content to a non-loopback endpoint requires WATERMARKS_REWRITE_ALLOW_REMOTE=1.

  • Offline mode for research harnesses. --offline forces MarkLLM/MarkDiffusion to load models from the local Hugging Face cache only, with trust_remote_code never enabled.

  • Resource caps. Config files are capped at 1 MiB; batch endpoints cap file counts (WATERMARKS_MAX_BATCH_FILES, default 50); parsers explicitly reject decompression-bomb-style inputs (the changelog references specific GHSA advisories fixed for PNG zTXt/iTXt, SVG/ODT, and sitemap DTD entity expansion).

  • Unrecognized input is refused, not guessed. Files that don't match a known text/image/container signature are classified unknown and rejected by /clean (400) rather than being decoded as UTF-8 and silently corrupted — a real bug class the project says it used to have and fixed.

  • Fail-soft, not fail-silent-success. Missing optional tools produce explicit warnings in the report (e.g., "install qpdf for a structural rewrite") rather than reporting a clean that didn't fully happen.


8. Strengths

  • The verifiable vs. best-effort distinction is threaded through the entire design — reports, docs, and API responses all separate what was deterministically removed from what was best-effort rewritten. This is the single best architectural decision in the project and the thing most similar tools skip.

  • Licensing discipline around third-party backends (pinned commits, non-published images, explicit LICENSE-absence handling) is unusually rigorous.

  • The hook vs. skill distinction (deterministic-on-write vs. cooperative-on-invocation) is a clear-eyed acknowledgment of what agent harnesses can and can't guarantee.

  • Format coverage is broad and the PDF cascading-pass design in particular reflects real production experience with where metadata actually survives naive stripping.

9. Limitations and open questions (a fair review should name these)

  • Layer B provides no removal guarantee, by the project's own repeated admission — it is a rewrite, not a certified bypass, and the README goes so far as to question the practice's own value proposition (why pay for a premium model's output only to have a cheaper model rewrite it away).

  • Pixel-domain image watermarks are only addressed via heavy, non-bundled, GPU-hungry external backends, and even those are described as "regenerating remover, not a guarantee."

  • C2PA soft binding (content that can re-link to a remote credentials manifest after local metadata is stripped) is explicitly out of scope.

  • Training-time backdoor watermarks are out of scope entirely, for all vendors.

  • The project depends on a fairly large number of pinned, occasionally licensing-encumbered upstream research repos (MarkLLM, MarkDiffusion, CtrlRegen, reverse-SynthID) — real supply-chain surface area even with the isolation described above.


10. Ecosystem

Two independent, unaffiliated projects are listed in the README for discoverability: MetaClean (a Rust/Tauri desktop GUI, MIT) and unmark-web (a static, MIT-licensed browser client that can optionally call this project's service). The README is careful to state it doesn't vouch for either.


11. Bottom line

Architecturally, this is a well-factored project: a clean separation between cooperative (skill) and deterministic (hook/service) enforcement, an honest three-layer taxonomy that doesn't oversell what statistical-watermark rewriting can do, and real engineering care around licensing isolation for the heavier optional backends. The project is explicit — repeatedly, not just in a disclaimer footer — that it's meant for hygiene and privacy on content you already own or are authorized to process, not for defeating provenance systems on content that isn't yours, and the architecture backs that framing up by refusing to claim certainty it can't verify.

Comments (0)

Login to post a comment.

Tomson Alex
Tomson Alex

Blogger

Passionate developer sharing knowledge about modern web technologies and best practices.

Subscribe to Tomson Alex's Newsletter

More from Tomson Alex

View profile

Building a Zero-Trust Internal API on AWS

A DynamoDB table, a read-only Lambda, an API Gateway locked to IAM auth, and an EC2 client with no SSH key: a full zero-trust internal API on AWS, with working Python and Node.js signing code and a private-API hardening step.

6 minJul 26