Skip to main content

dewatermark — Verification-first text watermark remover

PyPI version Python versions CI License: MIT GitHub stars

dewatermark removes suspicious hidden Unicode and helps test known statistical text watermarks. It can clean one string, scan a repository, locate a detector signal, or search for the smallest rewrite that clears named detectors. Basic Unicode cleanup runs locally, gives the same result every time, and needs no model or network connection.

For statistical LLM watermarks, it can run experiments and check the result with a detector built for that watermark. It never treats changed text as proof that every watermark is gone, and it does not claim to remove a vendor watermark when no compatible detector is available.

Try the browser playground (text stays in your browser) · View on PyPI · Explore integrations

Install

Python 3.9 or newer is required. The core package includes Unicode cleanup, analysis, repository scanning, and the CLI:

python -m pip install dewatermark
dewatermark --version

Current release: This page documents 0.7.0, including detector localization, detector-guided mitigation, and the KGW and Unigram reference packs.

Install optional features only when you need them:

python -m pip install "dewatermark[local]"   # local model-backed rewriting
python -m pip install "dewatermark[eval]"    # research and evaluation tools
python -m pip install "dewatermark[agents]"  # MCP server; Python 3.10+

Models are not downloaded automatically, even when an optional package is installed.

Clean hidden Unicode

import dewatermark

text = "he\u200bllo"  # contains an invisible zero-width character
clean = dewatermark.sanitize(text)

print(repr(text))   # 'he\u200bllo'
print(repr(clean))  # 'hello'

sanitize() returns a string. Its default safe profile removes or normalizes characters covered by the policy while preserving recognized emoji, right-to-left, and writing-system contexts.

Use analyze() when you want to inspect the text without changing it:

report = dewatermark.analyze(text)
print(report)

The aggressive profile also normalizes compatibility characters and look-alike letters. It is intentionally lossy, so use it only when that tradeoff is acceptable:

clean = dewatermark.sanitize(text, profile="aggressive")

Command line

python -c "print('he\u200bllo', end='')" | dewatermark sanitize
# hello

python -c "print('he\u200bllo', end='')" | dewatermark analyze
dewatermark check .

The first command writes cleaned text. analyze reports findings without changing the input. check scans files and changes nothing unless you pass --fix; it exits with status 1 when it finds actionable hidden Unicode.

Choose the right tool

Goal Start here
Remove clearly suspicious Unicode sanitize() or dewatermark sanitize
Inspect text without changing it analyze() or dewatermark analyze
Scan a repository dewatermark check PATH
Get a JSON-ready report of what changed remove(..., mode="sanitize").to_dict()
Try model-backed rewriting Statistical LLM watermarks
Verify a statistical watermark Verify with a detector
Find where a known detector sees a signal Locate and mitigate a known signal
Search for a verified, minimal rewrite Locate and mitigate a known signal
Review exact text and settings before applying Agents and automation
Use editors, CI, HTTP, MCP, or Docker Integrations

What the results mean

Unicode cleanup and statistical watermark testing are separate operations.

Result Meaning
unicode_sanitized Policy-covered characters were removed or normalized
mitigation_verified A named independent detector was positive before rewriting and clear afterward at its tested decision boundary, and every required quality check passed
mitigation_unverified Text changed and passed quality checks, but compatible verification was unavailable
unsupported_scheme The requested watermark cannot currently be tested
rejected_quality Rewritten candidates failed quality checks, so the original text was kept

These results do not identify who wrote the text and do not prove that it is universally watermark-free. See the assurance model for the full status contract.

Scan files and repositories

dewatermark check .
dewatermark check . --fix
dewatermark check . --format sarif --output dewatermark.sarif

The scanner reports the file, line, column, Unicode code point, and reason for each finding. --fix modifies files in place using atomic replacement; edit details are available in the JSON and Python reports. You can share one .dewatermark.toml policy across local development, pre-commit, CI, and the editor integrations.

See the integration guide for shared policies, ignore lists, checking only changed lines, pre-commit, and GitHub code scanning.

Privacy and safety

  • sanitize, analyze, repository scanning, and listing installed features run locally without a learned model.
  • Managed model downloads run only after dewatermark download-model, allow_model_download=True, or the matching environment setting.
  • Managed remote backends send text only when allow_remote_processing=True is set separately.
  • The default Unicode profile preserves contextual characters. The aggressive profile may change legitimate text.
  • Model-generated rewrites are treated as candidates. Detector-guided search accepts one only after quality checks and held-out verification pass; every other outcome returns the exact source.
  • Errors and result receipts omit source text and credentials. Configuration output also hides credentials. analyze() intentionally returns annotated input, so treat its output as sensitive.
  • Third-party Python extensions are trusted code and keep the permissions of the current process; this package is not an operating-system sandbox.

See the configuration guide and quality-check guide for advanced settings.

Statistical LLM watermarks (advanced)

Removing hidden Unicode is deterministic. Statistical watermark removal is experimental: the result depends on how the watermark was created, which detector checks it, and the text being tested.

remove() provides several research modes:

  • sanitize performs Unicode cleanup only.
  • bias_inversion and sira are experimental implementations inspired by the BIRA and SIRA papers.
  • paraphrase, full, and adversarial provide rewrite baselines.
  • auto chooses an available mode and falls back safely when a backend cannot run.

These modes are not proof against a vendor deployment. Use a compatible, independent detector for any removal claim. Model downloads and remote text processing remain disabled until enabled separately.

Verify statistical watermarks with a detector

dewatermark detectors list
dewatermark detectors doctor
dewatermark detectors conformance
dewatermark detectors packs

The built-in KGW-, Unigram-, and tournament-style detectors are small test cases for integration code, not production detectors. The packaged KGW and Unigram profiles score one exact, closed-vocabulary reference configuration; the KGW pack also keeps its older token example. The SynthID pack is only a disabled template until its required configuration and independent tests are supplied.

A passing conformance test means the integration passes its known test cases. It does not prove that the tool removes a production watermark. See the detector guide and reference detector guide.

Locate and mitigate a known signal

When you have a compatible detector, localize finds the character ranges that contribute to its result. Native detector ranges are preferred. Otherwise the tool scans bounded, overlapping windows and adjusts the confidence threshold so that scanning more windows does not make a positive result easier to obtain. Only a calibrated detector with compatible p-values and declared family-wise error control can produce a confirmatory localized result. Other ranges are labeled localized_exploratory: useful editing hints, not verification.

dewatermark localize --input input.txt --detector your-primary-detector

mitigate tries bounded candidate-generation strategies and ranks only candidates that pass the central quality checks. It returns changed text only when the primary detector clears and another calibrated, independent detector that was not used to guide the search also clears. Every other outcome returns the exact original text.

dewatermark mitigate \
  --input input.txt \
  --detector your-primary-detector \
  --verifier your-held-out-detector \
  --strategy your-rewrite-strategy \
  --consent

The detector and strategy names above are installed extensions, not bundled production services. The included KGW and Unigram profiles are exact, offline reference configurations for integration and conformance work. They are deliberately uncalibrated and cannot produce a production removal claim. See detector-guided mitigation for the Python API, budgets, subprocess strategy protocol, and acceptance rules.

Current Claude limitation

Anthropic has confirmed text marking for supported Claude models, but it has not published the detector and verification procedure needed for an independent test. dewatermark therefore returns unsupported; its capability metadata records status=unsupported_pending_spec. It does not claim that Unicode cleanup or a generic rewrite removes a Claude watermark.

Agents and automation

Use the review-before-apply API when a person or agent wants to inspect the exact text and settings before execution:

from dewatermark import apply_plan, create_plan, inspect_text, verify_text

text = "he\u200bllo"
inspection = inspect_text(text, detector="unicode")
plan = create_plan(text, mode="sanitize", detector="unicode")
applied = apply_plan(
    text,
    plan["plan_digest"],
    mode="sanitize",
    detector="unicode",
    consent=True,
)
verification = verify_text(
    text,
    applied["result"]["cleaned_text"],
    detector="unicode",
)

print(inspection["detector_evidence"]["status"])  # detected
print(verification["verification_status"])         # verified_cleared

The plan digest changes when the input or approved settings change, so apply_plan rejects stale or mismatched plans. It does not authenticate the approver or turn third-party Python plugins into sandboxed code.

The same workflow is available through the CLI, HTTP/OpenAPI, and MCP. See the agent workflow guide or locate the bundled skill with:

dewatermark skill path
dewatermark skill install --output ./remove-text-watermarks

Integrations

  • Browser and JavaScript source: use the browser playground, where text stays in the browser, or package the browser module from source.
  • Editors: local-only VS Code and JetBrains integrations are included.
  • Git hooks and CI: use pre-commit, the composite GitHub Action, or SARIF output for GitHub code scanning.
  • Services and agents: run the local HTTP/OpenAPI server, MCP stdio server, or generated API clients.
  • Containers: build the non-root Docker image; it does not start a network server unless you configure one.

All setup instructions are in the integration guide.

Test results and evaluation

The checked-in Unicode fixture report records 50 of 50 embedded examples removed across five known hidden-character families using the intentionally lossy aggressive profile. This only tests those examples; it says nothing about statistical or undocumented vendor watermarks.

The evaluation tools keep setup data separate from final test data and count errors as failures. No tracked statistical result currently satisfies the full benchmark protocol.

See the Unicode fixture report, evaluation guide, and benchmark protocol.

Extending and contributing

Rewriting backends, detectors, quality checks, and ways to split long input can be added without changing the package's core. Start with the extension guide, architecture, and contributor guide.

Good first contributions include detector adapters, editor integrations, Unicode examples from real systems, and independent benchmark replications. See the roadmap or open a feature proposal.

If the project is useful to you, consider starring it on GitHub.

Scope limits

  • The deterministic sanitizer covers known Unicode artifacts, not every possible text watermark.
  • Statistical results apply only to the named detector and configuration used for that run.
  • Editing text cannot erase records kept by a model provider or matching service.
  • This project handles text. It does not remove image watermarks, EXIF/XMP, C2PA, or document metadata.
  • Claude remains unsupported until a compatible public detector and procedure are available.

License

The package is MIT-licensed; see LICENSE. The generated confusables table includes Unicode data covered by the Unicode License v3.

Metadata

Release files for dewatermark 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dewatermark 0.7.0
File Size Uploaded
dewatermark-0.7.0.tar.gz 559.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dewatermark 0.7.0
File Interpreter ABI Platform
dewatermark-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.0 MB

Release files / dewatermark-0.7.0.tar.gz

Download URL dewatermark-0.7.0.tar.gz
Size 559.5 kB
Tags Source
SHA-256 checksum
How to use checksums
164bf7b02fc17baa225525e33b41a555e2049b0db55ea253d937aef1d4c867f1
BLAKE2b-256 checksum
How to use checksums
7665b20978aa8bd3d33c9123eee100b6258741edbb9f122c4ab6885a07010db7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release files / dewatermark-0.7.0-py3-none-any.whl

Download URL dewatermark-0.7.0-py3-none-any.whl
Size 480.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
11c228f06a4d29bcffafe7f61c8ae887dfe77aaaca2b7d5ec652f23768333172
BLAKE2b-256 checksum
How to use checksums
f01cdd43e7e391ecfc0bd6610dcf5f84ac84b8a1edf3663c86611d79bd61a738
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 19, 2026.

Transparency log

Release history Release notifications | RSS feed

0.8.0

2 release files

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page