Erase AI provenance & watermarks from your own text, images and documents.
See the codeStrip AI provenance signals — invisible Unicode, C2PA / EXIF / XMP metadata, and statistical text watermarks — from files you own. Locally, deterministically, with nothing phone-home about it.
Every AI system that touches your files tends to leave fingerprints: hidden Unicode codepoints pasted into text, provenance manifests and generator tags embedded in images and documents, or sampling-pattern watermarks threaded through the wording itself. Most of the time you never asked for those fingerprints — and once they're there, almost nothing removes them.
watermark-eraser is a batteries-included, dependency-free toolkit that finds and erases them. Point it at a file; it figures out what the file is, reports every mark it can see, cleans it, and re-verifies the result. It also ships as an agent skill so AI assistants can run the whole workflow for you.
For your own content. This is a privacy and hygiene tool, not a deception tool — please read the short ethics notes before using it.
🌐 Landing page: https://tekeburak.github.io/watermark-eraser/
| No dependencies | Pure Python 3.10+ stdlib. git clone and run — nothing to install. |
| Local by default | Files never leave your machine. Network features are loopback-only unless you explicitly opt in. |
| Deterministic | Every removal is counted and reported — codepoint by codepoint, chunk by chunk. |
| Honest | Verifiable removals are labeled verifiable; best-effort ones are labeled best-effort. No magic claims. |
| Broad formats | 16 file formats across images, documents, and text. |
| Agent-native | Ships as an installable skill with a full inspect → clean → verify workflow. |
| 234 tests | Hermetic suite (no network), CI on Ubuntu and Windows, pip-audit + CodeQL + Dependabot. |
git clone https://github.com/tekeburak/watermark-eraser.git
cd watermark-eraser
The two commands you'll use most — they route by format automatically (extension first, then magic bytes, including OOXML/ODF archive contents):
S=skills/remove-ai-marks/scripts
python3 $S/inspect_file.py report.docx # show every mark found (JSON: --json)
python3 $S/clean_file.py report.docx -o report.cleaned.docx
Both exit non-zero when marks are detected (inspect) or survive cleaning (clean), so they slot into scripts and CI pipelines. --in-place backs up the original to .bak before overwriting.
Useful single-purpose tools: inspect_text.py / clean_text.py (Unicode only), inspect_image.py / clean_image.py, rewrite_text.py (statistical rewrite hook), audit_dir.py / audit_website.py (bulk audits over a tree or a sitemap).
ln -sfn "$(pwd)/skills/remove-ai-marks" ~/.grok/skills/remove-ai-marks
Then just ask your agent: "strip the AI watermarks from these files" — or invoke /remove-ai-marks directly. The skill knows the full workflow, the ethics boundaries, and when rewriting is worth it.
If c2patool or exiftool are on your PATH, the tools detect and use them automatically (deeper C2PA inspection; reliable PDF stripping). Everything works without them.
Layer B is best-effort — but it doesn't have to be unmeasured. The optional google-deepmind/synthid-text backend (Apache-2.0, never bundled) scores whether text carries a SynthID-Text watermark for a known key/config. Scoring is a pure hashing computation over tokens — no model weights, no GPU, seconds per document:
make bootstrap-synthid-text # pinned checkout + minimal venv (uv-first)
# Score any text (~0.5 unwatermarked; higher = watermarked with these keys)
~/.watermark-eraser/synthid-text/.venv/bin/python \
skills/remove-ai-marks/scripts/score_synthid_text.py notes.txt
# Measure a full Layer B rewrite: before/after scores in the stats JSON
~/.watermark-eraser/synthid-text/.venv/bin/python \
skills/remove-ai-marks/scripts/rewrite_text.py draft.md \
--backend ollama --model llama3.2 --score-synthid-text --json-stats
Honest framing: it scores watermarks created with your keys — a self-verification harness for the rewrite method. Google's production Gemini keys are not public, and Claude's watermark has no published detector yet.
Layer A — invisible text carriers. Zero-width spaces, bidi overrides, tag characters, variation selectors, exotic space homoglyphs, private-use codepoints, dense carrier runs. Detected and scrubbed deterministically; every removal is counted. Legitimate load-bearing invisibles (emoji ZWJ sequences, script joiners in Persian/Devanagari, orthographic Arabic marks) are recognized and preserved.
Layer B — statistical text watermarks. SynthID-Text- and Kirchenbauer-class marks are encoded in which tokens the model picked, so the only real attack is rewording. rewrite_text.py drives that rewrite — paraphrase, humanize, back-translate, or structural — against a local model (Ollama or any OpenAI-compatible endpoint, loopback-only by default) or just prints the prompt for your agent to execute. Multi-candidate mode scores rewrites by lexical divergence, and the optional SynthID-Text backend turns "best-effort" into measured best-effort: before/after watermark scores on your own keyed content (--score-synthid-text). Section The honest part covers what this costs.
Layer C — file metadata. C2PA manifests, EXIF, XMP packets, document properties, customXml parts, generator tags — removed while image pixels and document text stay byte-compatible. The unified cleaners re-inspect their own output and report anything that survived.
| Category | Formats | What gets cleaned |
|---|---|---|
| Raster images | PNG, JPEG, WebP, TIFF, GIF | C2PA/JUMBF chunks, EXIF, XMP, text chunks, comment & marked app extensions |
| Vector / docs | SVG, PDF, DOCX, PPTX, XLSX, ODT, ODP, ODS | XMP/metadata blocks, docProps, customXml, generator & AI-ish meta fields |
| Text-ish | HTML, Markdown, plain text, code | generator meta, JSON-LD provenance, AI frontmatter keys + Layer A |
PDF cleaning prefers exiftool when available and falls back to a best-effort stdlib XMP strip (clearly flagged as degraded). Pixel-domain image watermarks are handled by an optional external CtrlRegen backend — see skills/remove-ai-marks/SKILL.md for setup, or run make bootstrap-ctrlregen.
Explicitly out of scope: C2PA soft binding, audio/video watermarks, and training-data backdoors. No tool honestly removes those today.
This toolkit deliberately parses untrusted files, so it's built like a security tool:
--in-place always backs up first.--force-text to override).127.0.0.0/8, not just 127.0.0.1), pin the connection to the validated address so DNS can't rebind mid-flight, refuse redirects (your API key never travels anywhere you didn't allow), and cap response sizes. Keys live in env vars, never argv.RLIMIT_AS/RLIMIT_FSIZE on every child process. All tunable via WATERMARKS_* env vars.Found something? Follow the private-disclosure policy in SECURITY.md.
Layer A and Layer C removals are verifiable: the tool counts what it removed and re-inspects the output.
Layer B is best-effort, and anyone telling you otherwise is selling something. A statistical watermark lives in the wording, so removing it means rewording — a lot of it, sentence by sentence. Every rewrite trades some of the original's voice and precision for mark resistance. If the plan is "generate with a premium model, then rewrite with a cheap one to strip marks," ask whether generating with the cheap model directly wouldn't have been simpler. Layer B earns its keep when you want the premium model's drafting and have a hygiene requirement to satisfy — use a non-origin model for the rewrite (origin models may re-stamp), and know that no tool can certify an undetectable result.
Vendor context (August 2026): Claude output has carried an embedded statistical text watermark plus signed C2PA file metadata since 2026-08-02 (EU AI Act Art. 50), and Google's SynthID is deployed across Gemini with detection in Search/Chrome and a cross-provider Cloud API. Detection tooling for Claude marks is not public yet — so for Layer B you can verify the method (SynthID-Text scorer on your own keyed watermarks) but not Claude-marked text directly. Details and sources: references/vendor-notes.md.
Contributions are genuinely welcome — this project runs on careful parsing and paranoia about edge cases, and fresh eyes find both. Read the contributor guide →
The short version:
git checkout -b fix/my-changeuv sync && uv run python -m pytest (preferred; uv-lockfile-exact) or pip install -r requirements.txt with your usual venv. All 260 tests must pass; they're hermetic, no networkmain — CI must be green; a code-owner review merges itGood first contributions: new format parsers (pattern: magic sniff + inspect + strip + programmatic fixtures), Layer A codepoint coverage, and docs clarity. Please keep the stdlib-first, no-network-in-tests philosophy, and keep it a tool for content people own. By participating you agree to the Code of Conduct.
MIT — see LICENSE.
score_synthid_text.py, rewrite_text.py --score-synthid-text): hashing-based before/after scoring of Layer B rewrites against watermarks created with your own keys — CPU-only, no model weights, pinned upstream checkout with a minimal dependency setreferences/vendor-notes.md and references/how-claude-marks.mdInitial public release.
inspect_file.py / clean_file.py routers over 16 formats (PNG, JPEG, WebP, TIFF, GIF, SVG, PDF, DOCX, PPTX, XLSX, ODT, ODP, ODS, HTML, Markdown, text) with magic-byte detection including OOXML/ODF archive contents5 commits
Python
89.0%
HTML
3.9%
Shell
3.4%
CSS
2.8%
Erase AI provenance & watermarks from your own text, images and documents.
See the codeStrip AI provenance signals — invisible Unicode, C2PA / EXIF / XMP metadata, and statistical text watermarks — from files you own. Locally, deterministically, with nothing phone-home about it.
Every AI system that touches your files tends to leave fingerprints: hidden Unicode codepoints pasted into text, provenance manifests and generator tags embedded in images and documents, or sampling-pattern watermarks threaded through the wording itself. Most of the time you never asked for those fingerprints — and once they're there, almost nothing removes them.
watermark-eraser is a batteries-included, dependency-free toolkit that finds and erases them. Point it at a file; it figures out what the file is, reports every mark it can see, cleans it, and re-verifies the result. It also ships as an agent skill so AI assistants can run the whole workflow for you.
For your own content. This is a privacy and hygiene tool, not a deception tool — please read the short ethics notes before using it.
🌐 Landing page: https://tekeburak.github.io/watermark-eraser/
| No dependencies | Pure Python 3.10+ stdlib. git clone and run — nothing to install. |
| Local by default | Files never leave your machine. Network features are loopback-only unless you explicitly opt in. |
| Deterministic | Every removal is counted and reported — codepoint by codepoint, chunk by chunk. |
| Honest | Verifiable removals are labeled verifiable; best-effort ones are labeled best-effort. No magic claims. |
| Broad formats | 16 file formats across images, documents, and text. |
| Agent-native | Ships as an installable skill with a full inspect → clean → verify workflow. |
| 234 tests | Hermetic suite (no network), CI on Ubuntu and Windows, pip-audit + CodeQL + Dependabot. |
git clone https://github.com/tekeburak/watermark-eraser.git
cd watermark-eraser
The two commands you'll use most — they route by format automatically (extension first, then magic bytes, including OOXML/ODF archive contents):
S=skills/remove-ai-marks/scripts
python3 $S/inspect_file.py report.docx # show every mark found (JSON: --json)
python3 $S/clean_file.py report.docx -o report.cleaned.docx
Both exit non-zero when marks are detected (inspect) or survive cleaning (clean), so they slot into scripts and CI pipelines. --in-place backs up the original to .bak before overwriting.
Useful single-purpose tools: inspect_text.py / clean_text.py (Unicode only), inspect_image.py / clean_image.py, rewrite_text.py (statistical rewrite hook), audit_dir.py / audit_website.py (bulk audits over a tree or a sitemap).
ln -sfn "$(pwd)/skills/remove-ai-marks" ~/.grok/skills/remove-ai-marks
Then just ask your agent: "strip the AI watermarks from these files" — or invoke /remove-ai-marks directly. The skill knows the full workflow, the ethics boundaries, and when rewriting is worth it.
If c2patool or exiftool are on your PATH, the tools detect and use them automatically (deeper C2PA inspection; reliable PDF stripping). Everything works without them.
Layer B is best-effort — but it doesn't have to be unmeasured. The optional google-deepmind/synthid-text backend (Apache-2.0, never bundled) scores whether text carries a SynthID-Text watermark for a known key/config. Scoring is a pure hashing computation over tokens — no model weights, no GPU, seconds per document:
make bootstrap-synthid-text # pinned checkout + minimal venv (uv-first)
# Score any text (~0.5 unwatermarked; higher = watermarked with these keys)
~/.watermark-eraser/synthid-text/.venv/bin/python \
skills/remove-ai-marks/scripts/score_synthid_text.py notes.txt
# Measure a full Layer B rewrite: before/after scores in the stats JSON
~/.watermark-eraser/synthid-text/.venv/bin/python \
skills/remove-ai-marks/scripts/rewrite_text.py draft.md \
--backend ollama --model llama3.2 --score-synthid-text --json-stats
Honest framing: it scores watermarks created with your keys — a self-verification harness for the rewrite method. Google's production Gemini keys are not public, and Claude's watermark has no published detector yet.
Layer A — invisible text carriers. Zero-width spaces, bidi overrides, tag characters, variation selectors, exotic space homoglyphs, private-use codepoints, dense carrier runs. Detected and scrubbed deterministically; every removal is counted. Legitimate load-bearing invisibles (emoji ZWJ sequences, script joiners in Persian/Devanagari, orthographic Arabic marks) are recognized and preserved.
Layer B — statistical text watermarks. SynthID-Text- and Kirchenbauer-class marks are encoded in which tokens the model picked, so the only real attack is rewording. rewrite_text.py drives that rewrite — paraphrase, humanize, back-translate, or structural — against a local model (Ollama or any OpenAI-compatible endpoint, loopback-only by default) or just prints the prompt for your agent to execute. Multi-candidate mode scores rewrites by lexical divergence, and the optional SynthID-Text backend turns "best-effort" into measured best-effort: before/after watermark scores on your own keyed content (--score-synthid-text). Section The honest part covers what this costs.
Layer C — file metadata. C2PA manifests, EXIF, XMP packets, document properties, customXml parts, generator tags — removed while image pixels and document text stay byte-compatible. The unified cleaners re-inspect their own output and report anything that survived.
| Category | Formats | What gets cleaned |
|---|---|---|
| Raster images | PNG, JPEG, WebP, TIFF, GIF | C2PA/JUMBF chunks, EXIF, XMP, text chunks, comment & marked app extensions |
| Vector / docs | SVG, PDF, DOCX, PPTX, XLSX, ODT, ODP, ODS | XMP/metadata blocks, docProps, customXml, generator & AI-ish meta fields |
| Text-ish | HTML, Markdown, plain text, code | generator meta, JSON-LD provenance, AI frontmatter keys + Layer A |
PDF cleaning prefers exiftool when available and falls back to a best-effort stdlib XMP strip (clearly flagged as degraded). Pixel-domain image watermarks are handled by an optional external CtrlRegen backend — see skills/remove-ai-marks/SKILL.md for setup, or run make bootstrap-ctrlregen.
Explicitly out of scope: C2PA soft binding, audio/video watermarks, and training-data backdoors. No tool honestly removes those today.
This toolkit deliberately parses untrusted files, so it's built like a security tool:
--in-place always backs up first.--force-text to override).127.0.0.0/8, not just 127.0.0.1), pin the connection to the validated address so DNS can't rebind mid-flight, refuse redirects (your API key never travels anywhere you didn't allow), and cap response sizes. Keys live in env vars, never argv.RLIMIT_AS/RLIMIT_FSIZE on every child process. All tunable via WATERMARKS_* env vars.Found something? Follow the private-disclosure policy in SECURITY.md.
Layer A and Layer C removals are verifiable: the tool counts what it removed and re-inspects the output.
Layer B is best-effort, and anyone telling you otherwise is selling something. A statistical watermark lives in the wording, so removing it means rewording — a lot of it, sentence by sentence. Every rewrite trades some of the original's voice and precision for mark resistance. If the plan is "generate with a premium model, then rewrite with a cheap one to strip marks," ask whether generating with the cheap model directly wouldn't have been simpler. Layer B earns its keep when you want the premium model's drafting and have a hygiene requirement to satisfy — use a non-origin model for the rewrite (origin models may re-stamp), and know that no tool can certify an undetectable result.
Vendor context (August 2026): Claude output has carried an embedded statistical text watermark plus signed C2PA file metadata since 2026-08-02 (EU AI Act Art. 50), and Google's SynthID is deployed across Gemini with detection in Search/Chrome and a cross-provider Cloud API. Detection tooling for Claude marks is not public yet — so for Layer B you can verify the method (SynthID-Text scorer on your own keyed watermarks) but not Claude-marked text directly. Details and sources: references/vendor-notes.md.
Contributions are genuinely welcome — this project runs on careful parsing and paranoia about edge cases, and fresh eyes find both. Read the contributor guide →
The short version:
git checkout -b fix/my-changeuv sync && uv run python -m pytest (preferred; uv-lockfile-exact) or pip install -r requirements.txt with your usual venv. All 260 tests must pass; they're hermetic, no networkmain — CI must be green; a code-owner review merges itGood first contributions: new format parsers (pattern: magic sniff + inspect + strip + programmatic fixtures), Layer A codepoint coverage, and docs clarity. Please keep the stdlib-first, no-network-in-tests philosophy, and keep it a tool for content people own. By participating you agree to the Code of Conduct.
MIT — see LICENSE.
score_synthid_text.py, rewrite_text.py --score-synthid-text): hashing-based before/after scoring of Layer B rewrites against watermarks created with your own keys — CPU-only, no model weights, pinned upstream checkout with a minimal dependency setreferences/vendor-notes.md and references/how-claude-marks.mdInitial public release.
inspect_file.py / clean_file.py routers over 16 formats (PNG, JPEG, WebP, TIFF, GIF, SVG, PDF, DOCX, PPTX, XLSX, ODT, ODP, ODS, HTML, Markdown, text) with magic-byte detection including OOXML/ODF archive contents5 commits
Python
89.0%
HTML
3.9%
Shell
3.4%
CSS
2.8%