dewatermark — Open-Source AI Text Watermark Remover
Find and remove hidden Unicode text watermarks locally, then test published statistical LLM watermark mitigations with reproducible, quality-constrained experiments. Use it from Python, CLI, pre-commit, GitHub code scanning, HTTP/OpenAPI, Docker, or an MCP-compatible AI agent.
Try the private browser playground · Install from PyPI · Explore integrations
If this saves you time, consider starring the repository—it helps other developers find a careful alternative to unverifiable universal-removal claims.
It does not claim to remove an Anthropic/Claude-specific watermark. Anthropic has not publicly documented a deployed Claude text-watermark scheme or detector. Unknown vendor systems, provider-side retrieval, and semantic provenance may remain detectable after rewriting.
What it does
- Removes unambiguous zero-width, tag-block, variation-selector, and exotic-space covert channels with a deterministic safe profile.
- Offers explicitly lossy compatibility and cross-script confusable folding for Latin-oriented text through the aggressive profile.
- Mitigates published statistical watermark families through local or explicitly authorized remote rewriting, with deterministic quality gates and fallbacks.
- Provides forensic analysis, dry-run planning, batch and async APIs, a provider extension system, and a matched-control evaluation harness.
- Scans repositories, emits SARIF, and integrates with pre-commit, HTTP/OpenAPI, Docker, and MCP-compatible AI agents.
Reproducible proof, with explicit scope
The deterministic aggressive-profile fixture benchmark removes all 50 embedded payloads across five Unicode covert-channel families.
| Fixture family | Removed |
|---|---|
| Zero-width binary | 10/10 |
| Variation selectors | 10/10 |
| Tags block | 10/10 |
| Cross-script homoglyphs | 10/10 |
| Exotic spaces | 10/10 |
| Total | 50/50 |
See the tracked report and rerun it with
PYTHONPATH=src python eval/run_eval.py --skip-statistical. This fixture result
is not evidence about statistical or undisclosed vendor watermarks.
Unicode cleanup is deterministic. Statistical mitigation depends on the named scheme, detector, model, configuration, text length, and quality constraint.
Install
The current stable release is v0.3.0; v0.4.0 is under development:
pip install dewatermark
pip install "dewatermark[local]" # local self-information scorer + BIRA
pip install "dewatermark[eval]" # evaluation models
pip install "dewatermark[agents]" # MCP server on Python 3.10+
Install the latest development version from main only when you need unreleased
changes:
pip install "git+https://github.com/cyzanfar/text-watermark-remover.git"
Quickstart
import dewatermark
text = "he\u200bllo"
# Safe by default: removes unambiguous covert controls without folding valid
# emoji shaping, RTL controls, compatibility characters, or confusables.
clean = dewatermark.sanitize(text)
forensics = dewatermark.analyze(text)
result = dewatermark.remove(text, mode="auto")
print(result.cleaned_text)
print(result.report)
# Explicitly lossy Latin-text canonicalization:
canonical = dewatermark.sanitize(text, profile="aggressive")
Model downloads are also opt-in. Preload one explicitly with
dewatermark download-model, or set allow_model_download=True when constructing
a configuration.
Command line and agents
The CLI never prompts and supports stdin, stable JSON, JSONL batches, dry runs, and capability discovery:
printf 'he\u200bllo' | dewatermark sanitize --format json
dewatermark capabilities
dewatermark remove --mode auto --dry-run --format json
dewatermark remove --mode sanitize --format jsonl < requests.jsonl
dewatermark schema
dewatermark check .
dewatermark check . --format sarif --output dewatermark.sarif
dewatermark serve # local HTTP + OpenAPI
dewatermark-mcp # MCP stdio server
Python callers can inspect dewatermark.capabilities() and
dewatermark.plan(mode) without network access, model loading, or downloads.
remove_many() preserves batch order and aremove() integrates with async agent
runtimes. Results carry a versioned JSON schema with explicit status, backend,
fallback, warnings, and stage details.
Repository scanning
dewatermark check reports exact files, lines, columns, categories, and code
points. It never modifies files unless --fix is passed explicitly. SARIF output
can appear as annotations in GitHub code scanning, and the included pre-commit
hook blocks hidden characters before they enter a commit. See the
integration recipes.
Remote processing is deny-by-default because source text may be sensitive:
from dewatermark import Dewatermark, DewatermarkConfig
dw = Dewatermark(DewatermarkConfig(
lm_backend="fireworks",
fireworks_api_key="fw-...",
allow_remote_processing=True,
))
result = dw.remove(text, mode="bias_inversion", beta=6.0)
Removal modes
sanitize: Unicode cleanup only.safeis the default profile;aggressiveenables lossy NFKC/confusable folding.bias_inversion: BIRA-style surprisal proxy set, negative logit bias, adaptive bias backoff/restarts, and deterministic quality gates.sira: proportional self-information masking, reference rewrite, targeted infill, and quality-gated acceptance.paraphrase/full: structural and cross-lingual rewriting baselines.adversarial: best-of-N SIRA candidates. Its surprisal score is a weak selection heuristic, not proof that a watermark was removed.auto: BIRA first, quality/failure-aware fallback to paraphrasing, then sanitize-only when no rewrite backend is usable.
Long inputs are split at paragraph/sentence boundaries and reconstructed exactly at chunk boundaries. Local models use CUDA/MPS automatically when available. A 7B–14B instruction model is recommended for rewrite quality; the 0.5B default demonstrates the mechanism but should not be expected to reproduce published attack results.
Safety and quality behavior
Every generated candidate is rejected if it is empty, truncated/expanded past configured bounds, repetitive, contains mask placeholders, or drops numbers, URLs, email addresses, or quoted strings. These deterministic checks catch catastrophic failures but do not prove semantic equivalence; production users should add NLI, claim-QA, and human review for consequential content.
Important configuration:
| Environment variable | Default | Purpose |
|---|---|---|
DEWATERMARK_LM_BACKEND |
auto |
local, fireworks, or automatic selection |
DEWATERMARK_LOCAL_LM |
Qwen/Qwen2.5-0.5B-Instruct |
Local scorer/rewriter |
DEWATERMARK_ALLOW_MODEL_DOWNLOAD |
false |
Explicit model acquisition consent |
DEWATERMARK_FIREWORKS_AI_API_KEY |
— | Fireworks scorer/rewriter |
DEWATERMARK_LLM_API_KEY |
— | OpenAI-compatible paraphrase/SIRA endpoint |
DEWATERMARK_ALLOW_REMOTE_PROCESSING |
false |
Consent to transmit source text |
DEWATERMARK_SANITIZE_PROFILE |
safe |
safe or explicitly lossy aggressive |
DEWATERMARK_MAX_CHUNK_CHARS |
12000 |
Rewrite chunk bound |
DEWATERMARK_MAX_INPUT_CHARS |
1000000 |
Per-request input bound |
DEWATERMARK_MAX_REMOTE_CALLS |
16 |
Per-operation remote-call budget |
DEWATERMARK_MAX_OUTPUT_TOKENS |
2048 |
Generated-token budget |
DEWATERMARK_MAX_CONCURRENCY |
4 |
Batch worker bound |
DEWATERMARK_REQUEST_TIMEOUT |
120 |
Per-request timeout ceiling |
DEWATERMARK_QUALITY_MIN_LENGTH_RATIO |
0.70 |
Candidate acceptance bound |
DEWATERMARK_QUALITY_MAX_LENGTH_RATIO |
1.35 |
Candidate acceptance bound |
Unprefixed v0.2 names remain compatibility aliases during the 0.3 release line.
Evaluation
The harness uses matched transformed nulls, refuses to estimate an empirical FPR
without at least ceil(1/FPR) null samples, provides confidence intervals, and
supports length sweeps. Independent official implementations can be connected
through the JSON command-adapter contract in eval/adapters.py.
dewatermark-eval --skip-statistical --output results.md
# Expensive research run. 1e-5 FPR is deliberately reported as not estimable
# unless at least 100,000 matched nulls are supplied.
DEWATERMARK_ALLOW_REMOTE_PROCESSING=true dewatermark-eval \
--samples 100 --null-samples 1000 \
--lengths 100,250,500,1000,2000 \
--modes bias_inversion,sira,full \
--model-revision MODEL_COMMIT --allow-model-download \
--json-output results.json --checkpoint progress.jsonl
The evaluation guide and research plan explain the evidence requirements and why no universal efficacy result is claimed. The runner uses strict failure handling by default and records configuration, package versions, hardware, prompt hashes, checkpoints, and machine-readable results.
Extending and contributing
Third-party scorers and rewriters can implement the structural interfaces in
dewatermark.protocols, register in-process, or publish through the
dewatermark.providers entry-point group. See extension documentation,
architecture, and contributor guide.
Good first contributions include detector adapters, editor integrations, Unicode fixtures from real systems, and benchmark replications. Review the roadmap or open a feature proposal.
Scope limitations
- No public Claude-specific scheme is available to target or verify.
- Retrieval-based provenance cannot be removed from text.
- Token surprisal does not reliably identify semantic, post-processing, cryptographic, learned, or undisclosed watermarks.
- Detector success must be stated for a named scheme, key/configuration, operating threshold, text length, and quality constraint—not as universal “watermark removal.”
License
The package code is MIT-licensed; see LICENSE. The generated confusables table incorporates Unicode data distributed under the Unicode License v3.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dewatermark-0.4.0.tar.gz.
File metadata
- Download URL: dewatermark-0.4.0.tar.gz
- Upload date:
- Size: 83.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f957f74ae7aac929da64eb3fe0f942d4321f44579a53268e6c8514e82dd9ee92
|
|
| MD5 |
c152c55c955aaf17e5892e648e201b32
|
|
| BLAKE2b-256 |
0e7152a24077e37bd4d66e8619cbdc3d4da29628f0284d81f695c90caf9a4c1d
|
Provenance
The following attestation bundles were made for dewatermark-0.4.0.tar.gz:
Publisher:
publish.yml on cyzanfar/dewatermark
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dewatermark-0.4.0.tar.gz -
Subject digest:
f957f74ae7aac929da64eb3fe0f942d4321f44579a53268e6c8514e82dd9ee92 - Sigstore transparency entry: 2495283944
- Sigstore integration time:
-
Permalink:
cyzanfar/dewatermark@3e9bf6477d5cb4e0ee429865fac4f0be84d138b0 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/cyzanfar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e9bf6477d5cb4e0ee429865fac4f0be84d138b0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dewatermark-0.4.0-py3-none-any.whl.
File metadata
- Download URL: dewatermark-0.4.0-py3-none-any.whl
- Upload date:
- Size: 83.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1785ebfc9939dd3d2e923bc8e4dee63172461902964e0bd8701a42d86bb6e128
|
|
| MD5 |
f091169d67ae75908395ca6781a27023
|
|
| BLAKE2b-256 |
e90976a2ba0927e3e9a1de84c13f08607b44f97b365080b0de91802103a83bdb
|
Provenance
The following attestation bundles were made for dewatermark-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on cyzanfar/dewatermark
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dewatermark-0.4.0-py3-none-any.whl -
Subject digest:
1785ebfc9939dd3d2e923bc8e4dee63172461902964e0bd8701a42d86bb6e128 - Sigstore transparency entry: 2495283952
- Sigstore integration time:
-
Permalink:
cyzanfar/dewatermark@3e9bf6477d5cb4e0ee429865fac4f0be84d138b0 -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/cyzanfar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e9bf6477d5cb4e0ee429865fac4f0be84d138b0 -
Trigger Event:
release
-
Statement type: