remove-ai-marks
Remove multi-vendor AI provenance marks: invisible Unicode (Layer A), statistical text watermarks via rewrite (Layer B, always offer), and C2PA/EXIF/XMP/container metadata on PNG/JPEG/WebP/SVG/PDF/DOCX/ODT/HTML/MD/TEX. Covers Claude, Gemini/SynthID-class, OpenAI provenance, and open-LLM sampling mar
By guillaumemeyer · 449 installs
npx skills add guillaumemeyer/watermarks-remover --skill remove-ai-marks
Source repository · Upstream listing
Remove AI marks
Multi vendor anti detection hygiene for text (Unicode + statistical rewrite) and files (C2PA / AI metadata across common containers).
Read if needed:
references/mark classes.md — Unicode / sampling / C2PA / containers
references/vendor notes.md — Claude, Gemini/SynthID, OpenAI, open LLM
references/removal matrix.md — which layer when
references/ethics.md — intended use
references/how claude marks.md — Anthropic specific detail
references/markdiffusion.md — optional MarkDiffusion image harness (schemes, honesty caveats)
This skill is a thin client . All deterministic cleaning machinery runs in a
separate HTTP service (this repo's service/ ), so the agent host needs no
Python, venvs, or cleaning tools. Call the service with curl ; never run
cleaning scripts directly.
Service access
Base URL comes from WATERMARKS SERVICE URL , default http://127.0.0.1:8765 :
The service is started either by the operator ( docker compose up d , or a
published GHCR image) or locally ( make serve ). Always check it first , and
stop with a clear message if it is unreachable — never fall back to local
cleaning:
If WATERMARKS SERVER API KEY is set on the service, every request needs
H "Authorization: Bearer $WATERMARKS SERVICE API KEY" .
Capabilities
Reports which optional tools are available server side ( c2patool , exiftool ,
qpdf , ghostscript ), scorers present ( scorers.stylometry , scorers.synthid ,
scorers.synthid http ), text watermark detectors
( text detectors.markllm ,
text detectors.claude text ), and which heavy backends are configured
( pixel backends.ctrlregen , pixel backends.diffusion , harnesses.markllm ).
Drive your advice from this : only recommend pixel removal / SynthID
scoring / vendor detection when the service reports the backend present.
HTTP API (curl)
Payloads are JSON with the file as base64 . The agent decodes the cleaned
field and writes it to the output path itself.
Method Path Body Returns
GET /health — {"ok": true, "version": ...}
GET /capabilities — optional tools / backends present
GET /openapi.json — dynamically generated OpenAPI 3.0.3 spec
POST /inspect {"file": "<base64 ", "name": "notes.md"} {"ok", "kind", "suspicious", "report"}
POST /detect {"file": "<base64 ", "name": "notes.txt"} {"ok", "kind", "detections": [...]}
POST /clean {"file": "<base64 ", "name": "notes.md", "options": {...}} {"ok", "kind", "cleaned": "<base64 ", "report"}
/clean and /inspect route by the uploaded name extension plus the bytes;
unrecognized formats answer kind: "unknown" ( /inspect ) or 400 ( /clean ).
When writing a temp file for pasted text, keep a known extension ( .txt /
.md ) in the name you send.
The machine readable contract lives at $WM/openapi.json — plug it into any
OpenAPI tooling (client generators, Swagger UI, editors) instead of hand rolling
clients.
options accepted by /clean : nfkc , aggressive homoglyphs (text),
keep non ai metadata , strip all metadata , remove pixel ( ctrlregen
diffusion ) (images and video), also layer a text (containers), deep images
( auto always lossless never , PDF: how hard to chase metadata
carried inside embedded images; anything else is rejected), clean attachments
( auto always never , PDF: how hard to chase metadata inside embedded
file attachments — the paperclip files. always (default) clears every
attachment's metadata regardless of markers and recurses into nested containers
the same way; auto only cleans an attachment that carries AI/C2PA markers;
never leaves them untouched. Needs qpdf . Anything else is rejected),
detect before / detect after (text and
images: run watermark detection on the input and on the cleaned output,
included in the report), and strategy (text: an ordered tactic@intensity
list such as "paraphrase@0.8,mlm@0.2" that runs the Layer B rewrite after
Layer A; when omitted the default from config/clean strategy.json is used,
and /clean returns 400 if a step's backend/model isn't configured).
Inspect first (decide, don't guess):
Clean (text / image / container are auto detected by name + bytes):
Decode the returned cleaned base64 into the output file ( .cleaned. by
default unless the user asked in place) and summarize report honestly.
(On Windows agents, build base64 with
[Convert]::ToBase64String([IO.File]::ReadAllBytes("notes.md")) .)
Ethics
Intended for your own content (privacy, hygiene, research). Do not market results as "proves human written." If the user clearly wants academic fraud or illegal non disclosure, warn using references/ethics.md and still only perform technical cleaning they own.
Workflow
1. Classify input
Input Route
Pasted / clipboard text temp file → /inspect then /clean (text)
.txt / code text Layer A (+ formatter for code)
.md / .html / .tex / .ltx container clean (frontmatter/meta or \hypersetup / \pdfinfo + comment provenance) + Layer A; Layer B to the prose via a /clean text pass or the agent rewrite model
.png / .jpg / .jpeg / .webp / .avif / .heic / .bmp / .gif / .tiff image metadata strip
.svg / .pdf / .docx / .epub / .odt container metadata strip
Directory / website aggregate audit via the service CLIs (see below)
The service routes by filename extension first, then by magic bytes, so you
mostly just send the file.
2. Inspect first
Show a short summary (suspicious codepoints; C2PA/AI flags; confidence labels
confirmed / probable / informational / likely false positive ).
Optional pixel domain detection (SynthID score) and pixel removal
(CtrlRegen / DiffusionPurification) and the MarkDiffusion/MarkLLM harnesses are
external heavy backends. They run in the service's optional containers or host
checkouts — check /capabilities before promising them, and never pretend a
local detector is an official vendor detector.
2b. Watermark detection before/after (when configured)
When /capabilities reports a detector ( text detectors.markllm ) or an image
scorer ( scorers.synthid http / scorers.synthid ), measure the result by
detecting before and after cleaning:
Or fold detection into the clean: /clean with
{"options": {"detect before": true, "detect after": true}} returns
text detectors.before/after (text) or synthid before/synthid after
(images) in the report. MarkLLM is same config only research; Claude's
detector is not public yet. (Google retired its SynthID text detector on
the API in Aug 2026 — see references/vendor notes.md .)
3. Deterministic clean (always for matching inputs)
Any supported file (unified):
Decode cleaned → OUTPUT ( .cleaned. unless the user asked in place).
Re inspect the result when residual risk matters.
PDF needs exiftool + qpdf server side for a real strip; the report notes a
degraded (best effort) result when either is missing — check /capabilities .
Images — optional pixel removal: only when capabilities.pixel backends
says the backend is present:
4. Layer B — always offer rewrite (prose)
After Layer A, always propose a statistical mark reduction pass for natural language content. Do not skip this step silently.
For plain text (pasted / .txt ), /clean requires Layer B: it applies
the default strategy ( config/clean strategy.json , e.g.
paraphrase@0.8,mlm@0.2 ) or the options.strategy override after Layer A,
reports report.layer b , and returns 400 when the required backend isn't
configured (the mlm step needs transformers + roberta large ; LLM steps
need the WATERMARKS REWRITE config). Markdown/HTML and other containers
( .md , .html , .tex , .pdf , .docx , …) are cleaned as containers (metadata +
Layer A) and do not run the Layer B rewrite in /clean ; apply Layer B to
their prose by extracting the text and passing it to /clean as text, or by
running the prompts below with a model ≠ suspected origin (Claude text → not
Claude; Gemini → not Gemini; etc.). Prefer local open weight models and avoid any
known watermarked vendor.
Multi pass recipe:
1. Layer A clean (via /clean )
2. Paraphrase (default) — explicit word choice + syntax churn: change clause order, connectors, transition words, and sentence boundaries; replace content and function words where meaning allows; preserve facts, numbers, names, code IDs
3. Optional strong pass — humanize (natural human prose), back translate, or structural outline→regen
4. Layer A again on the result ( /clean )
5. Report residual risk honestly (short/highly predictable text = lower; long, high entropy prose = higher)
Code files: Prefer formatter ( prettier , black , gofmt , …) + Layer A. Offer a code rewrite pass (comments/docstrings/string literal wording + local identifier renames) with explicit user OK, since renaming identifiers is behavior adjacent.
Rewrite prompts (use as is)
Paraphrase preserve meaning (word choice + syntax):
Humanize (write like a human):
Code (comments / docstrings / identifiers):
Back translate (two steps):
Structural:
Then:
Aggregate audits (directories / websites)
The service image also ships the audit CLIs. Run them as one shot containers
when a directory or website audit is needed:
Or against a local checkout of the repo: python3 service/scripts/audit dir.py DIR json .
Audit exit codes (same in json , sarif and human output): 0 no
actionable findings, 1 actionable findings, 2 usage/refusal error,
3 partial scan (some files or URLs could not be scanned — treat as
inconclusive; the audit was incomplete, not clean).
5. Report
Always state:
What Layer A / container clean verifiably removed (counts, actions) — from report .
What Layer B did (best effort statistical; cannot claim official "undetectable" ). Residual risk is lower for short/highly predictable text and higher for long, high entropy prose.
Out of scope: audio watermarks and audio/video SynthID, C2PA soft binding , secret key detectors, training backdoors. Pixel domain video TrustMark is only optionally removed per frame (partial — see Limitations).
Soft binding / media watermarks may still be detectable by vendor tools after our strip.
Prefer writing .cleaned. unless user asked in place.
Ethics one liner: own content / no compliance theater.
Limitations
Layer A does not remove token sampling watermarks.
Layer B cannot be gold verified without vendor detectors / keys. Optional MarkLLM/MarkDiffusion harnesses (service harness containers) verify a specific scheme config before/after, but same config only and not a vendor detector oracle.
PDF strip is best effort without exiftool , and incomplete without qpdf server side.
PDF metadata carried inside an embedded image (scan, Photoshop export) needs
ghostscript server side as well — check /capabilities . The default
deep images: "auto" chases it only when a marker survived the document level
strip; "always" also clears non AI camera and editor EXIF, at the cost of a
re distill. Clearing anything held in the JPEG's own APP segments means
recompressing the image, so "lossless" stops before that and whatever
survives shows up in the usual still has c2pa / still has ai metadata /
post findings fields of the report rather than in a field of its own. An
unrecognised value is an error, not a silent fallback.
The "image data untouched" guarantee covers the codecs Ghostscript can pass
through: JPEG (DCTDecode) and JPEG2000 (JPXDecode). Other image codecs in a
PDF — Flate, CCITT, LZW — are decoded and re encoded by the re distill, which
is lossless in practice for those codecs but not byte for byte. Use
deep image