Model or dataset
guillaumemeyer/watermarks-remover avatar
guillaumemeyer/watermarks-remover

watermarks-remover: an agent skill and stdlib Python service for stripping AI provenance marks

A privacy-first app that strips AI watermarks from content you own.

22,090 stars2,547 forksPythonMIT

At a glance

What is it?
The project splits the work into deterministic Unicode and file-metadata cleaning on one side and statistical text-watermark rewriting on the other. The hook is the reliable half; the skill depends on the model choosing to cooperate.
Who is it for?
Adopt it if you write files through an agent and want a deterministic PostToolUse hook that reports or strips provenance marks before they land in a repository; the check mode is the safer starting point. Skip it if your only exposure is chat text, because the README states no hook can rewrite the assistant's message before you read it, and the skill is only an instruction the model may ignore.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What watermarks-remover actually removes, layer by layer

The README describes three distinct targets, and they fail in different ways. Layer A covers invisible Unicode, exotic spaces, bidirectional control characters and tag characters. That is a deterministic string problem: the same input always produces the same output, and a Python script can handle it without a model in the loop. Layer B covers statistical text watermarks, the token-sampling schemes where a generator biases word choice according to a key. There is no byte to delete here, because the mark is a property of the word distribution, so the project routes this through an agent rewrite plus an optional rewrite_text.py hook. The third target is file metadata: C2PA manifests, EXIF, XMP and document properties across PNG, JPEG, WebP, AVIF, HEIC, BMP, GIF, TIFF, SVG, PDF, DOCX, XLSX, PPTX, EPUB, ODT, HTML, Markdown, MP4/MOV/M4A/M4V, WAV, MP3 and FLAC. The vendor list is stated at class level rather than as a detector matrix: Claude, Gemini and SynthID-Text, OpenAI provenance surfaces, and open-LLM Kirchenbauer-style green-list and keyed-Gumbel or EXP marks. That wording matters. The project does not claim a per-vendor oracle for every model, and the .env.example records that Google removed SynthID text watermarking from the Generative Language API in Aug 2026, which is why the gemini-synthid-text detector was removed. Anyone expecting a live vendor detection endpoint for Gemini text should read that note before filing an issue.

The hook is the deterministic half, and the README is honest about the other half

The architectural argument in the README is worth taking seriously: a skill is an instruction, and the model is the thing producing the marks, so a skill cannot guarantee anything. A hook is executed by the harness on every matching tool call. The plugin registers a PostToolUse hook on Write|Edit|MultiEdit|NotebookEdit and runs service/scripts/hook_written_file.py against the file the agent just wrote. Two modes exist, following the pre-commit convention of checking by default. In check mode the hook reports provenance marks and leaves the file alone, exiting 2 so the findings reach the model. In clean mode it strips marks in place and tells the model the file on disk changed. Detection reuses audit_lib's scan_file and is_actionable, so the hook, the pre-commit gate and the CI SARIF export agree on what counts as actionable, and cleaning shells out to clean_file.py rather than duplicating logic. The clean path writes to a sibling temp file and swaps only on a real difference, so an already-clean file keeps its mtime and does not retrigger file watchers. That is a small detail with real consequences for anyone running a watcher or a build loop. The stated limit is equally concrete: no hook can rewrite the assistant's chat message before you read it. Claude Code's Stop hook receives last_assistant_message read-only, and there is no pre-send filter for final responses. So the deterministic guarantee covers files the agent writes and the pre-commit gate, and nothing else.

Installing the skill and running a first clean

The skill ships no code. It is markdown that drives the service over HTTP, so the agent host needs no Python, but the service does need to be running and reachable at http://127.0.0.1:8765 unless WATERMARKS_SERVICE_URL says otherwise. The installer is stdlib-only and requires Python 3.10 or newer. This command installs the full service-backed skill into Claude Code's personal skill directory, which honors CLAUDE_CONFIG_DIR.

bash
python3 install_skill.py --skill remove-ai-marks --target claude-code

The README lists four targets: claude-code for ~/.claude/skills/<skill>, claude-project with --project-dir PATH for PATH/.claude/skills/<skill>, cowork which produces dist/<skill>.zip for upload under Customize then Skills, and cursor, which is the default, landing in ~/.cursor/skills/<skill>. Passing --list prints the shipped skills, which are remove-ai-marks (full, service-backed) and clean-user-facing-text (text only, self-contained). Existing installations are preserved unless you pass --force, replacement is staged first, and the previous install is kept as a uniquely named backup. On Windows the README says to use py install_skill.py instead of python3, and install-skill.sh is the macOS and Linux wrapper. Before writing anything, the installer validates the skill against the Agent Skills packaging rules that claude.ai uploads and the Skills API enforce: spec-only frontmatter, a lowercase hyphenated name of at most 64 characters matching the directory, and a non-empty description of at most 1024 characters. The Cowork bundle must also fit the 30 MB upload limit.

Bringing up the service itself is a separate step, and compose.yaml shows the core profile mapping port 8765 to loopback only:

bash
docker compose up --build -d

That starts wr-core alone. The harness profile adds the markllm and markdiffusion harnesses, and the heavy profile adds ctrlregen and synthid, which build from source locally because the README states they bake in upstream code that is not publicly redistributable under an all-rights-reserved or non-commercial Research License. The heavy and harness images are one-shot CLIs: up starts them with --help to confirm the image, and real jobs run through docker compose run, for example docker compose run --rm wr-ctrlregen /data/shot.png -o /data/out.png. If you would rather wire the hook by hand, the README gives this settings.json fragment:

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
          }
        ]
      }
    ]
  }
}

On Windows, python3 becomes py. Note the README's warning about the plugin's own hook command: it deliberately does not interpolate ${user_config.hook_mode}, because Claude Code refuses to run a hook referencing an option the user has never opened /plugin manage to set, so a declared default would not satisfy it and the hook would silently never run on a fresh install. Set the mode through Hook mode in /plugin manage, or with WATERMARKS_HOOK_MODE=clean in the environment.

Where it does not help: chat transcripts, video platforms, and statistical marks

The most important limitation is stated by the project itself. Files the agent writes are covered deterministically. Text that only ever exists in the chat transcript is not. If your concern is that a model's reply carries a statistical watermark, the skill workflow is the only path, and it depends on the model deciding to invoke the skill. That is a real gap, not a documentation oversight, and the README says the same limit applies to Cursor rules. The second gap is statistical removal quality. Layer B is an agent rewrite, and the README describes it as detection-guided rewriting in the v0.6.0 notes, but nothing in the repository establishes how reliably a rewrite destroys a keyed watermark or how much meaning is preserved. Treat the rewrite as best-effort and verify the output yourself. The third gap is scope. The file table is long, but it is a list of containers the project parses, not a promise that every provenance field inside every container is understood. If your content lives on a platform rather than on disk, this tool has no role: it operates on files and on text you pass to it, and the topics list mentions platforms only as vendor ecosystems, not as integrations. The fourth consideration is legal rather than technical, and the README frames the whole project as being for privacy and hygiene on content you own. Stripping provenance from material you do not own is outside that framing, and the MIT licence on the code does not settle questions about the content you run through it.

How it compares to C2PA tooling and to pre-commit metadata scrubbers

The closest comparison is the c2patool family and the C2PA SDKs. Those are built to read, validate and attach manifests, and they treat a manifest as a signed claim to be verified rather than a mark to be removed. watermarks-remover treats C2PA as one of several provenance surfaces to strip, alongside EXIF, XMP and document properties, and it does so through the same scan-then-clean path used for Unicode. The difference in approach shows up in the output: a C2PA tool tells you whether a claim is valid and who signed it, while this project tells you whether a file is actionable and then removes the mark. If your job is to prove provenance, this is the wrong tool, and the README makes no claim to verification. The second comparison is a pre-commit metadata scrubber such as exiftool in a pre-commit configuration. That covers files on their way into git, and this project also ships .pre-commit-hooks.yaml, so the two overlap there. The difference is the agent loop. A pre-commit hook only sees what you stage; the PostToolUse hook sees what the agent writes, which is earlier in the pipeline and covers files that never reach a commit. The third comparison is doing nothing and relying on the model vendor to keep marks benign. That is a policy choice, not a technical one, and the .env.example note about SynthID text detection being retired from the Generative Language API is a reminder that vendor-side detection surfaces can disappear without notice.

Maintenance, releases and licence implications

The repository is not archived, and the last push was on 2026-09-13, so describing it as actively developed is accurate on the evidence available. The release cadence is visible in the notes: v0.5.0 on 2026-08-14 added service and Docker distribution, an HTTP API and verification harnesses; v0.6.0 on 2026-08-26 added wider format coverage, Layer A hardening, plugin and hook distribution, and detection-guided rewriting; v0.7.0 on 2026-09-03 rewrote the /clean Layer B path and added a watermark-stealing module plus audio and video watermark removal. Three releases in three weeks is a fast cadence, and it also means the surface is moving. Anyone pinning to a version should expect the hook contract and the skill packaging rules to be the parts most likely to shift, since those track Claude Code's plugin and Agent Skills behaviour rather than the project's own preferences. The name change from remove-claude-marks to remove-ai-marks is documented as a migration, and the slash alias /remove-claude-marks is still documented, which suggests the old name has not been fully retired from user habits. On licensing, the code is MIT. The compose.yaml comment is the part to read carefully: ctrlregen and synthid bake in upstream code that the repository describes as not publicly redistributable, under all-rights-reserved or non-commercial Research License terms, so those images build from source locally and are never pushed to GHCR. The core service image is published to ghcr.io. If you enable the heavy profile, the licence terms of those upstream components apply to that part of the stack separately from the MIT licence on the rest, and that is a question for your own counsel rather than something the README resolves.

Editorial conclusion

Adopt it if you write files through an agent and want a deterministic PostToolUse hook that reports or strips provenance marks before they land in a repository; the check mode is the safer starting point. Skip it if your only exposure is chat text, because the README states no hook can rewrite the assistant's message before you read it, and the skill is only an instruction the model may ignore. Verify first that your host is covered by install_skill.py, that the service is reachable on 127.0.0.1:8765, and that the formats you care about appear in the Layer A and file tables, since the README does not document rollback for in-place cleaning.

Frequently asked questions

What does watermarks-remover do?

It is an agent skill plus a stdlib Python service that strips multi-vendor AI provenance marks from text and files you own. It handles invisible Unicode and exotic characters deterministically, statistical text watermarks through an agent rewrite, and C2PA, EXIF, XMP and document properties across a long list of image, document, audio and video formats.

How do I install the watermarks-remover skill in Claude Code?

Run python3 install_skill.py --skill remove-ai-marks --target claude-code, which lands the skill in ~/.claude/skills/ and honors CLAUDE_CONFIG_DIR. The skill ships no code, so the service must also be running and reachable at http://127.0.0.1:8765 unless you set WATERMARKS_SERVICE_URL.

Does watermarks-remover work without Docker?

The installer is stdlib-only and needs Python 3.10 or newer, and the README says the core service works with no configuration at all. Docker is one distribution path shown in compose.yaml, not a requirement for installing the skill itself.

Can watermarks-remover clean text that only appears in the chat?

No. The README states that no hook can rewrite the assistant's chat message before you read it, because Claude Code's Stop hook receives last_assistant_message read-only and there is no pre-send filter for final responses. Text that only exists in the transcript depends on the skill workflow, which the model may decline to invoke.

Why was the gemini-synthid-text detector removed from watermarks-remover?

The .env.example notes that Google removed SynthID text watermarking from the Generative Language API in Aug 2026, so the gemini-synthid-text detector was removed. The comment adds that a vendor detector can be re-added if Google exposes detection again.

What licence does watermarks-remover use?

The repository is MIT licensed. However, the compose.yaml notes that the ctrlregen and synthid heavy-profile images bake in upstream code described as not publicly redistributable under all-rights-reserved or non-commercial Research License terms, so those build locally and are never pushed to GHCR.

Official sources

  1. guillaumemeyer/watermarks-remover on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes