[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-94636":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":15,"stars7d":15,"stars30d":15,"stars90d":15,"forks30d":15,"starsTrendScore":15,"compositeScore":16,"rankGlobal":10,"rankLanguage":10,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":18,"hasPages":18,"topics":20,"createdAt":10,"pushedAt":10,"updatedAt":28,"readmeContent":29,"aiSummary":30,"trendingCount":15,"starSnapshotCount":15,"syncStatus":31,"lastSyncTime":32,"discoverSource":33},94636,"watermarks-remover","guillaumemeyer\u002Fwatermarks-remover","guillaumemeyer","Strip multi-vendor AI provenance marks: Unicode text hygiene, statistical rewrite hooks, and C2PA\u002Fmetadata from PNG\u002FJPEG\u002FSVG\u002FPDF\u002FDOCX\u002FHTML\u002FMD","https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover",null,"Python",12560,1348,16,0,69.39,"MIT License",false,"main",[21,22,23,24,25,26,27],"agent-skill","ai","c2pa","claude","provenance","synthid","watermark","2026-08-24 04:01:22","```\n_ _ _ ____ ___ ____ ____ _  _ ____ ____ _  _ ____    ____ ____ _  _ ____ _  _ ____ ____\n| | | |__|  |  |___ |__\u002F |\\\u002F| |__| |__\u002F |_\u002F  [__  __ |__\u002F |___ |\\\u002F| |  | |  | |___ |__\u002F\n|_|_| |  |  |  |___ |  \\ |  | |  | |  \\ | \\_ ___]    |  \\ |___ |  | |__|  \\\u002F  |___ |  \\\n```\n\n# watermarks-remover\n\n\u003C!-- logo: figlet -d .figlet -f cybermedium -w 120 \"watermarks-remover\" -->\n\n[![CI](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Factions\u002Fworkflows\u002Fci.yml)\n[![Release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Fguillaumemeyer\u002Fwatermarks-remover)](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases)\n[![Stars](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fstars\u002Fguillaumemeyer\u002Fwatermarks-remover)](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Fstargazers)\n[![Forks](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fforks\u002Fguillaumemeyer\u002Fwatermarks-remover)](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Fforks)\n\nAgent skill + stdlib Python scripts to strip **multi-vendor AI provenance marks** from text and files — for privacy and hygiene on content **you own**.\n\n| Layer | Target | How |\n| --- | --- | --- |\n| **A** | Invisible Unicode, exotic spaces, bidi, tag chars | Deterministic Python scripts |\n| **B** | Statistical (token-sampling) text watermarks | Agent rewrite + optional `rewrite_text.py` hook |\n| **Files** | C2PA \u002F EXIF \u002F XMP \u002F doc props | PNG, JPEG, SVG, PDF, DOCX, ODT, HTML, Markdown |\n\nVendors \u002F ecosystems (class-level): **Claude**, **Gemini \u002F SynthID-Text**, **OpenAI** provenance surfaces, **open-LLM** Kirchenbauer-style marks.\n\n**Latest release:** [v0.4.0](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.4.0)\n\nSkill path: [`skills\u002Fremove-ai-marks\u002F`](skills\u002Fremove-ai-marks\u002F)  \n(migration: formerly `remove-claude-marks`; slash alias `\u002Fremove-claude-marks` still documented)\n\n## Install (agent skill)\n\n```bash\n# Grok Build \u002F project-local\nmkdir -p .grok\u002Fskills\nln -sfn \"$(pwd)\u002Fskills\u002Fremove-ai-marks\" .grok\u002Fskills\u002Fremove-ai-marks\n\n# User-global Grok\nmkdir -p ~\u002F.grok\u002Fskills\nln -sfn \"$(pwd)\u002Fskills\u002Fremove-ai-marks\" ~\u002F.grok\u002Fskills\u002Fremove-ai-marks\n```\n\nInvoke with `\u002Fremove-ai-marks` or ask to “strip AI watermarks \u002F C2PA \u002F Claude marks \u002F SynthID-class text.”\n\nOptional system tools (auto-used when present):\n\n| Tool | Role |\n| --- | --- |\n| [`c2patool`](https:\u002F\u002Fgithub.com\u002Fcontentauth\u002Fc2pa-rs\u002Ftree\u002Fmain\u002Fcli) | Inspect C2PA manifests |\n| [`exiftool`](https:\u002F\u002Fexiftool.org\u002F) | Residual metadata strip (esp. **PDF**) |\n\nCore scripts need **Python 3.10+** stdlib only. Layer B model calls are optional.\n\n## Quick use (scripts)\n\n```bash\nSCRIPTS=skills\u002Fremove-ai-marks\u002Fscripts\n\n# Unified inspect \u002F clean\npython3 \"$SCRIPTS\u002Finspect_file.py\" draft.md\npython3 \"$SCRIPTS\u002Fclean_file.py\" draft.md -o draft.cleaned.md\npython3 \"$SCRIPTS\u002Fclean_file.py\" photo.png -o photo.cleaned.png\npython3 \"$SCRIPTS\u002Fclean_file.py\" notes.docx -o notes.cleaned.docx\n\n# Text Layer A\npython3 \"$SCRIPTS\u002Finspect_text.py\" draft.md\npython3 \"$SCRIPTS\u002Fclean_text.py\" draft.md -o draft.cleaned.md --stats\n\n# Layer B rewrite hook (default: print prompt only — no model required)\npython3 \"$SCRIPTS\u002Frewrite_text.py\" draft.md --backend print-prompt --strength paraphrase\n# Optional local Ollama (loopback only by default — remote endpoints require\n# WATERMARKS_REWRITE_ALLOW_REMOTE=1 or --allow-remote):\n# WATERMARKS_REWRITE_BACKEND=ollama WATERMARKS_REWRITE_MODEL=llama3.2 \\\n#   python3 \"$SCRIPTS\u002Frewrite_text.py\" draft.md -o draft.rewritten.md\n# API keys are read from WATERMARKS_REWRITE_API_KEY only (never argv).\n\n# Images\npython3 \"$SCRIPTS\u002Finspect_image.py\" shot.png\npython3 \"$SCRIPTS\u002Fclean_image.py\" shot.png -o shot.cleaned.png\n```\n\n## Optional SynthID pixel scoring\n\n`inspect_image.py` and `clean_image.py` can report a pixel-domain SynthID\nconfidence score when an external checkout of\n[`aloshdenny\u002Freverse-SynthID`](https:\u002F\u002Fgithub.com\u002Faloshdenny\u002Freverse-SynthID)\nis available. The scorer is **not bundled**: it is loaded at runtime from your\ncheckout, and its code remains under the upstream project's non-commercial\nResearch License.\n\n### Option 1: one-command bootstrap (no Docker)\n\n```bash\nSCRIPTS=skills\u002Fremove-ai-marks\u002Fscripts\n\n# Clones upstream, creates a venv, and installs scorer-only dependencies.\n\"$SCRIPTS\u002Fsetup_synthid.sh\"\n\n# Score an image (default checkout: ~\u002Freverse-SynthID).\nREVERSE_SYNTHID_DIR=~\u002Freverse-SynthID \\\n~\u002Freverse-SynthID\u002F.venv\u002Fbin\u002Fpython \"$SCRIPTS\u002Fscore_synthid.py\" shot.png\n\n# Or surface the score from inspect \u002F clean (same venv Python).\nREVERSE_SYNTHID_DIR=~\u002Freverse-SynthID \\\n~\u002Freverse-SynthID\u002F.venv\u002Fbin\u002Fpython \"$SCRIPTS\u002Finspect_image.py\" shot.png\n```\n\n`setup_synthid.sh` accepts `--dir PATH`, `--ref REF`, and `--full` (install the\nfull upstream `requirements.txt`, which adds `torch`\u002F`diffusers` for the\nupstream VAE bypass this project does not use).\n\n### Option 2: local Docker build\n\n```bash\nmake docker-synthid-build\n# Run unprivileged and with a read-only rootfs; the scorer only needs to read\n# \u002Fdata and write to stdout\u002Ftmp.\ndocker run --rm \\\n  --user \"$(id -u):$(id -g)\" \\\n  --read-only --tmpfs \u002Ftmp \\\n  -v \"$(pwd):\u002Fdata\" \\\n  watermarks-remover-synthid-scorer \u002Fdata\u002Fshot.png\n```\n\nThe image is built locally from the upstream source at build time. It is not\npublished, so it does not redistribute the upstream code.\n\nV4 scoring uses `artifacts\u002Fspectral_codebook_v4.npz` from the upstream checkout\n(~220 MB). This is **detection\u002Fscoring only** — it does not remove pixel\nwatermarks.\n\n## Optional CtrlRegen pixel removal\n\nFor **pixel-domain** image watermarks (SynthID-class, StegaStamp, Tree-Ring,\nStableSignature), an optional external backend runs the CtrlRegen pipeline\n(ControlNet + DINOv2 IP-Adapter controllable regeneration). The backend is\n[`mertizci\u002Fnoai-watermark`](https:\u002F\u002Fgithub.com\u002Fmertizci\u002Fnoai-watermark), a\nmaintained reimplementation of the ICLR 2025\n[CtrlRegen](https:\u002F\u002Farxiv.org\u002Fabs\u002F2410.05470) method with automatic tiling.\n\nThe backend is **not bundled** and ships no LICENSE file, so it is treated as\nall-rights-reserved: it is cloned at a pinned commit and loaded at runtime.\n\n### Bootstrap\n\n```bash\nSCRIPTS=skills\u002Fremove-ai-marks\u002Fscripts\n\n# Clones upstream (pinned commit), creates a venv, installs torch + deps.\n\"$SCRIPTS\u002Fsetup_ctrlregen.sh\"\n\n# Standalone removal (default checkout: ~\u002Fnoai-watermark).\nNOAI_WATERMARK_DIR=~\u002Fnoai-watermark \\\n~\u002Fnoai-watermark\u002F.venv\u002Fbin\u002Fpython \"$SCRIPTS\u002Fclean_ctrlregen.py\" shot.png -o shot.ctrlregen.png\n```\n\n### From `clean_image.py`\n\n```bash\nNOAI_WATERMARK_DIR=~\u002Fnoai-watermark \\\n~\u002Fnoai-watermark\u002F.venv\u002Fbin\u002Fpython \"$SCRIPTS\u002Fclean_image.py\" shot.png \\\n  -o shot.cleaned.png --remove-pixel ctrlregen\n```\n\nOrder of operations: metadata strip first, then CtrlRegen pixel removal, then\nan optional reverse-SynthID before\u002Fafter score (when `REVERSE_SYNTHID_DIR` is\nalso set).\n\n**Strength is conservative by default** (`--ctrlregen-strength 0.25`), because\nhigher strength removes more watermark but regenerates more of the image.\nDocumented presets: `0.15` minimal \u002F `0.25` default \u002F `0.35` balanced \u002F\n`0.5` aggressive \u002F `0.7` max (backend default is 0.5). `--ctrlregen-steps`\ndefaults to 50 (effective denoising steps ≈ steps × strength).\n\n### Image size (512×512 native limit)\n\nCtrlRegen is a 512×512 Stable Diffusion 1.5 ControlNet. The backend resolves\nthis for arbitrary inputs, so no extra tiling is exposed here:\n\n- **≤512 px:** single pass — center-crop\u002Fresize to 512, regenerate, resize back.\n- **>512 px:** automatic overlapping tiling (512 px tiles, 192 px overlap),\n  width\u002Fheight aligned to multiples of 8, then cosine-blended seams.\n- **Either path:** output is resized to the original size and color-matched to\n  the original image.\n\nVery large images (e.g. 4K) produce many tiles, so runs scale with tile count\n(slower and higher VRAM). Pre-downscale large inputs when practical; tile size\nand overlap are hardcoded upstream and are not exposed as flags.\n\n### Compute, gated models, and verification\n\nExpect ~10 GB of model downloads; a GPU is strongly recommended and CPU runs\nare slow. Some upstream models are gated, so export `HF_TOKEN` (env only —\nnever argv). `clean_ctrlregen.py` refuses to auto-install dependencies; run\n`setup_ctrlregen.sh` first.\n\nThere is no local detector for StegaStamp\u002FTree-Ring\u002FStableSignature, so the\nonly local signal is the reverse-SynthID score (a surrogate). When available,\n`clean_image.py --remove-pixel ctrlregen` reports that score before\u002Fafter; the\nofficial Google SynthID check remains the final authority.\n\n### Docker\n\n```bash\nmake docker-ctrlregen-build\ndocker run --rm -e HF_TOKEN=\"$HF_TOKEN\" \\\n  --user \"$(id -u):$(id -g)\" \\\n  -v \"$(pwd):\u002Fdata\" \\\n  watermarks-remover-ctrlregen \u002Fdata\u002Fshot.png -o \u002Fdata\u002Fshot.ctrlregen.png\n```\n\n## Coverage matrix\n\n| Channel | Claude | Gemini\u002FSynthID | OpenAI | Open-LLM |\n| --- | --- | --- | --- | --- |\n| Unicode \u002F edit-based text | Layer A | Layer A | Layer A | Layer A |\n| Statistical sampling text | Layer B best-effort | Layer B best-effort | Layer B if present | Layer B best-effort |\n| C2PA \u002F file metadata | Yes (listed formats) | Yes when present | Yes when present | Yes when present |\n| Pixel image marks | Out of scope | Optional SynthID score + CtrlRegen removal (external) | Out of scope | Optional CtrlRegen removal (external) |\n| Training backdoors | Out of scope | Out of scope | Out of scope | Out of scope |\n\nDetails: [`skills\u002Fremove-ai-marks\u002Freferences\u002Fvendor-notes.md`](skills\u002Fremove-ai-marks\u002Freferences\u002Fvendor-notes.md), [`mark-classes.md`](skills\u002Fremove-ai-marks\u002Freferences\u002Fmark-classes.md).\n\n---\n\n## How text marking works (short)\n\nModern LLM watermarks often hide a signal in **which tokens are chosen** (generative \u002F sampling bias), not only in invisible characters. Edit-based schemes inject Unicode or synonym rules. File schemes attach **C2PA** or generator metadata.\n\n- **Layer A** removes edit-based Unicode carriers (testable).\n- **Layer B** attacks sampling watermarks via heavy rewrite (best-effort; literature-standard attacks such as paraphrase \u002F back-translation).\n- **File cleaners** strip C2PA\u002FXMP\u002Fprops from supported containers.\n\nUntil vendors ship public detectors and keys, **no tool can honestly certify** “this fails the official check.” Reports must separate verifiable vs best-effort work.\n\nPrefer a **non-origin** model for Layer B (do not rewrite Claude text with Claude if you are trying to avoid re-stamping).\n\n---\n\n## Disclaimer: what removing a text watermark costs\n\nText watermarks live in **the wording itself**: the signal is spread across token choices, so nearly every sentence carries a little of it. Two consequences follow, and they are why Layer B is honestly described as *best-effort* rather than a magic eraser.\n\n1. **Removal means rewording, not restructuring.** Shuffling paragraphs, changing headings, or light touch-ups barely move the signal. Stripping a statistical mark requires rewriting a substantial fraction of the text — sentence by sentence, not section by section.\n\n2. **Rewording degrades the copy.** Any rewrite replaces the original word choices with the rewriting model's, which flattens tone, voice, and precision. On production copy (SEO, marketing, client work) that degradation is real and often visible to the people who care most about the writing. It is like taking text from a top-tier model and asking a less capable model to rewrite it from scratch: the result cannot exceed the rewrite model's ceiling.\n\nWhich leads to the honest full-circle question:\n\n> If the plan is to rewrite the text with a cheaper model anyway, why pay for a premium model in the first place? Generating directly with the cheaper model is simpler, cheaper, and produces the same — or better — end result.\n\nLayer B makes sense when you specifically want the premium model's **thinking and drafting** and accept a rewrite pass to satisfy a hygiene or privacy requirement — not as a cheap route to mark-free text.\n\n**When to skip Layer B:**\n\n- **Quality matters more than hygiene:** use the lossless path — Layer A Unicode scrub plus the file metadata cleaners — and keep the original prose.\n- **Rewriting anyway:** use a **non-origin** model (rewriting with the origin model can re-stamp the text), and remember residual risk remains — no tool can certify a vendor detector will fail.\n\n---\n\n## File formats\n\n| Format | Inspect | Clean |\n| --- | --- | --- |\n| PNG \u002F JPEG | C2PA chunks \u002F APP11, AI XMP hints | Drop metadata segments |\n| SVG | `\u003Cmetadata>`, XMP | Strip blocks |\n| PDF | Byte\u002FXMP + optional tools | **exiftool** preferred; degraded without it |\n| DOCX | docProps \u002F customXml | Scrub props, drop customXml |\n| ODT | meta.xml | Drop generator \u002F AI-ish meta |\n| HTML | meta, JSON-LD, data-ai* | Strip tags\u002Fattrs |\n| Markdown | YAML frontmatter AI keys | Drop keys + Layer A body |\n\nPixel-domain watermark **removal** is now available as an optional external CtrlRegen backend (see above); it is a regenerating remover, not a guarantee. **C2PA soft binding** (in-content watermark that can re-link a remote Content Credentials manifest after metadata is stripped) remains **out of scope**. Stripping hard-bound C2PA does **not** clear those channels.\n\n### Residual risk after a clean\n\nThis tool reports **verifiable** removals (Unicode counts, metadata actions) and **best-effort** Layer B rewrites. It cannot certify that vendor detectors will fail.\n\nTo check residual signals yourself (optional, external):\n\n| Channel | What we remove | What may remain | External check (examples) |\n| --- | --- | --- | --- |\n| Hard-bound C2PA \u002F EXIF \u002F XMP | Yes | Soft-bound \u002F pixel marks | [c2patool](https:\u002F\u002Fgithub.com\u002Fcontentauth\u002Fc2pa-rs\u002Ftree\u002Fmain\u002Fcli), [Content Credentials verify](https:\u002F\u002Fcontentcredentials.org\u002Fverify) |\n| SynthID-class media | Optional pixel removal (external CtrlRegen); local score otherwise | Audio\u002Fvideo watermark; residual pixel watermark after removal | Provider tools (e.g. [Google SynthID](https:\u002F\u002Fdeepmind.google\u002Fscience\u002Fsynthid\u002F) \u002F Vertex detector where offered); optional local [reverse-SynthID](https:\u002F\u002Fgithub.com\u002Faloshdenny\u002Freverse-SynthID) scorer |\n| Statistical text | Best-effort rewrite | Strong marks after light edit | No public universal detector; vendor tools when available |\n\nIndustry two-layer context (C2PA + imperceptible watermark): [Institute of AI PM guide](https:\u002F\u002Fwww.institutepm.com\u002Fknowledge-hub\u002Fai-content-provenance-watermarking).\n\n---\n\n## Removal options (summary)\n\n| Option | Removes | Notes |\n| --- | --- | --- |\n| Unicode scrub (Layer A) | ZWSP, bidi, tags, exotic spaces, … | Safe default for text |\n| Rewrite (Layer B) | Statistical token marks (best-effort) | Always offered by skill; costs style — see [Disclaimer](#disclaimer-what-removing-a-text-watermark-costs) |\n| Container\u002Fmetadata strip | File provenance | See format table |\n| CtrlRegen pixel removal (optional) | Pixel-domain image marks (SynthID-class, StegaStamp, Tree-Ring, StableSignature) | External backend; heavy compute; conservative strength default |\n| Open-weight local models | Avoid re-stamping with origin model | Operational alternative |\n\nMatrix: [`skills\u002Fremove-ai-marks\u002Freferences\u002Fremoval-matrix.md`](skills\u002Fremove-ai-marks\u002Freferences\u002Fremoval-matrix.md).\n\n## Ethics and disclaimer\n\nSee [`skills\u002Fremove-ai-marks\u002Freferences\u002Fethics.md`](skills\u002Fremove-ai-marks\u002Freferences\u002Fethics.md). For privacy and research on **your** content — not academic fraud or false “human-written” claims.\n\n**Responsible use:** This project is for content you own or are authorized to process. Users must adhere to local regulations and use it responsibly. The developers disclaim any liability for potential misuse by users.\n\n## Tests\n\n```bash\npython3 -m venv .venv && .venv\u002Fbin\u002Fpip install pytest\n.venv\u002Fbin\u002Fpython -m pytest          # or: make test\nmake smoke                          # quick CLI smoke on fixtures\n```\n\n## Changelog\n\n### [v0.4.0](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.4.0) — pixel removal, finding confidence, Windows & false-positive fixes\n\n**Optional CtrlRegen pixel removal (external backend)**\n\n- Optional pixel-domain watermark removal via an external `mertizci\u002Fnoai-watermark` checkout: `clean_ctrlregen.py` adapter + `setup_ctrlregen.sh` bootstrap (pinned commit, sparse checkout, venv, SHA verification), plus `Dockerfile.ctrlregen` and `make bootstrap-ctrlregen` \u002F `docker-ctrlregen-build` \u002F `smoke-ctrlregen`\n- `clean_image.py --remove-pixel ctrlregen` runs metadata strip → CtrlRegen removal → optional reverse-SynthID before\u002Fafter score; `inspect_image.py` hints at the flag on a high SynthID score\n- Conservative default strength `0.25` (presets 0.15\u002F0.25\u002F0.35\u002F0.5\u002F0.7); the 512×512-native pipeline is auto-tiled by the backend for larger images; the torch subprocess gets higher env-overridable resource caps\n- Backend is never bundled: `noai-watermark` ships no LICENSE file (treated as all-rights-reserved), and its auto-install\u002Frestart code paths are bypassed by using `CtrlRegenEngine` directly\n\n**Finding confidence and aggregate audits**\n\n- Findings are now classified `confirmed` \u002F `probable` \u002F `informational` \u002F `likely_false_positive`, exposed in text\u002Fimage\u002Fcontainer JSON and human reports\n- New `audit_dir.py` (recursive tree) and `audit_website.py` (sitemap discovery + crawl) aggregate reports; documented in SKILL.md\n\n**False-positive fixes**\n\n- DOCX: scan only `docProps`\u002F`customXml`, not the visible body (#14)\n- Text Layer A: preserve emoji `VS16`\u002F`ZWJ` after an emoji base; new `--strip-emoji-glue` paranoid flag (#22)\n- HTML: treat CMS generator tags as informational, not AI metadata (#13)\n- PDF: exclude stream payloads from the AI-marker byte scan (#13)\n- Inspect reports note unsupported\u002Fbest-effort paths\n\n**Windows support**\n\n- Gate POSIX-only `preexec_fn` and `os.fchmod` so writes and optional tools run on Windows (#15, #23)\n- Reconfigure stdio to UTF-8 so redirected Windows streams no longer raise on invisible Unicode; Windows CI leg + CLI smoke run (#23)\n\n**Docs and supply chain**\n\n- README CtrlRegen section + research references (CtrlRegen, UnMarker, forensic-stealth caveat), responsible-use disclaimer; SKILL\u002Fmatrix\u002Fvendor-notes\u002Fethics updates\n- Dependabot config + security-path CODEOWNERS; bump scipy\u002Fnumpy\u002Fopencv-python\u002Fscikit-learn\u002Fpywavelets and the base image to Python 3.14-slim\n- Mock-based CtrlRegen tests (no torch in CI)\n\n### [v0.3.2](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.3.2) — security hardening (safe writes, HTTP client, CI supply chain)\n\n- **Safe, atomic output writes**: every cleaner now writes via temp-file + atomic rename (`safe_write_bytes` \u002F `safe_write_text`), refuses symlinked destinations, and creates `.bak` backups through the same safe path — pre-placed symlinks (e.g. in `\u002Ftmp` or download dirs) can no longer redirect a clean write onto an arbitrary file\n- **`rewrite_text.py` HTTP client hardening**: redirects are refused outright, so an API key in the `Authorization` header can never be re-sent to an unvalidated host; non-loopback endpoints are **denied by default** (opt in with `--allow-remote` or `WATERMARKS_REWRITE_ALLOW_REMOTE=1`); only http(s) schemes are accepted; `--api-key` was removed — keys are env-only via `WATERMARKS_REWRITE_API_KEY`\n- **Resource caps**: default max input 1 GiB → 256 MiB, new 64 MiB stdin cap, DOCX\u002FODT zip budget 512 MiB → 128 MiB, and `RLIMIT_AS`\u002F`RLIMIT_FSIZE` applied to exiftool\u002Fc2patool\u002FSynthID subprocesses (all caps env-overridable)\n- **Supply chain**: CI actions SHA-pinned with `permissions: contents: read`, pinned dev deps (`requirements-dev.txt`), a `pip-audit` step, and a new CodeQL workflow; the Docker image now runs as an unprivileged user with pip pinned\n- **Scorer deps**: Pillow bumped 10.4.0 → 12.3.0 (24 known CVEs); API usage verified against the pinned upstream commit\n- Tests: 18 new security regression tests (60 total, all passing)\n\n### [v0.3.1](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.3.1) — stronger Layer B statistical-watermark rewrite\n\n- `rewrite_text.py` default paraphrase now performs an explicit **word-choice + syntax** attack (clause order, connectors, transition words, sentence boundaries, function words) rather than a generic rewrite\n- New `--strength humanize`: zero-shot \"write like a human\" pass targeting formulaic AI-style phrasing\n- New `--strength code`: rewrites comments, docstrings, and string literals, and renames local identifiers while preserving behavior and public API names\n- Structural pass now emits \"natural, varied human prose\" instead of AI-typical \"clear professional style\"\n- New `--temperature` (default `0.9`) for both Ollama and OpenAI-compatible backends\n- New `--candidates N`: generates N rewrites and selects the most lexically diverged (bigram Jaccard distance) with a length-drift guard\n- Stronger model hygiene: prefer local open-weight models and avoid any known-watermarked vendor, not just the suspected origin\n- Residual-risk reporting now distinguishes short\u002Fhighly predictable text (lower risk) from long, high-entropy prose (higher risk)\n- Docs updated in `SKILL.md`, `removal-matrix.md`, and `vendor-notes.md`; tests cover new prompts, divergence scoring, and candidate selection\n\n### [v0.3.0](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.3.0) — optional SynthID pixel scoring\n\n- Optional pixel-domain SynthID scorer via an external [`aloshdenny\u002Freverse-SynthID`](https:\u002F\u002Fgithub.com\u002Faloshdenny\u002Freverse-SynthID) checkout (`score_synthid.py`); surfaced in `inspect_image.py` \u002F `clean_image.py` with `REVERSE_SYNTHID_DIR` or `--synthid-dir`\n- `setup_synthid.sh` bootstrap (scorer-only dependencies; `--full` installs upstream requirements); `Dockerfile.synthid` plus `make docker-synthid-build` \u002F `docker-synthid-help`\n- Makefile `smoke-synthid` and `bootstrap-synthid` targets\n- Tests for the scorer adapter, CLI unavailable path, JSON parsing, and runtime errors\n- Docs: detection\u002Fscoring only (no pixel removal); upstream code is not bundled and remains under its non-commercial Research License\n\n### [v0.2.0](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.2.0) — c2patool false-positive fix\n\n- `image_meta.py`: `has_manifest` no longer flags `Error: No claim found` \u002F `No JUMBF data found` as a manifest (operator-precedence bug: the negative markers now veto every positive branch)\n- New `tests\u002Ftest_c2patool_report.py` (4 cases: no claim, no JUMBF, genuine manifest, tool absent)\n- Docs: fixed `c2patool` links (repo moved to `contentauth\u002Fc2pa-rs`); added a disclaimer on the quality cost of text-watermark removal\n\n### [v0.1.0](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.1.0) — packaging polish + provenance honesty\n\n- `Makefile` (`test` \u002F `smoke` \u002F `install-skill`) and `pytest.ini`\n- Fixture samples for Markdown, HTML, SVG; PDF degraded-clean test\n- Docs: industry **two-layer** model (hard-bound C2PA vs soft binding \u002F SynthID-media)\n- README residual-risk table + links to external verify tools\n- Reference: Institute of AI PM C2PA\u002FSynthID guide\n- Soft-binding and pixel\u002Faudio\u002Fvideo watermarks explicitly out of scope in skill\u002Fmatrix\u002Fethics\n\n### [v0.0.1](https:\u002F\u002Fgithub.com\u002Fguillaumemeyer\u002Fwatermarks-remover\u002Freleases\u002Ftag\u002Fv0.0.1) — initial multi-vendor release\n\n- Agent skill `remove-ai-marks` (replaces Claude-only `remove-claude-marks`)\n- **Layer A:** invisible Unicode \u002F bidi \u002F tag chars \u002F space homoglyphs (`inspect_text` \u002F `clean_text`)\n- **Layer B:** rewrite guidance + optional `rewrite_text.py` (print-prompt, Ollama, OpenAI-compatible)\n- **Files:** C2PA\u002FAI metadata strip for PNG, JPEG, SVG, PDF, DOCX, ODT, HTML, Markdown\n- Unified `inspect_file.py` \u002F `clean_file.py`\n- Multi-vendor docs (Claude, Gemini\u002FSynthID-class, OpenAI, open-LLM)\n- Stdlib-first scripts; optional `c2patool` \u002F `exiftool`\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\n## References\n\n- [How Claude marks AI-generated content](https:\u002F\u002Fsupport.claude.com\u002Fen\u002Farticles\u002F16266773-how-claude-marks-ai-generated-content) (Anthropic)\n- Dathathri et al., [*Scalable watermarking for identifying large language model outputs*](https:\u002F\u002Fwww.nature.com\u002Farticles\u002Fs41586-024-08025-4) (SynthID-Text, Nature 2024)\n- Google AI for Developers, [*SynthID safeguards*](https:\u002F\u002Fai.google.dev\u002Fresponsible\u002Fdocs\u002Fsafeguards\u002Fsynthid) (Gemini API docs)\n- [C2PA](https:\u002F\u002Fc2pa.org\u002F) \u002F [c2patool](https:\u002F\u002Fgithub.com\u002Fcontentauth\u002Fc2pa-rs\u002Ftree\u002Fmain\u002Fcli)\n- Kirchenbauer et al., [*A Watermark for Large Language Models*](https:\u002F\u002Farxiv.org\u002Fabs\u002F2301.10226)\n- Zhang et al., [*Watermarks in the Sand: Impossibility of Strong Watermarking for Generative Models*](https:\u002F\u002Farxiv.org\u002Fabs\u002F2311.04378) (ICML 2024)\n- [google-deepmind\u002Fsynthid-text](https:\u002F\u002Fgithub.com\u002Fgoogle-deepmind\u002Fsynthid-text) (research reference; not used for detection here)\n- [aloshdenny\u002Freverse-SynthID](https:\u002F\u002Fgithub.com\u002Faloshdenny\u002Freverse-SynthID) (research reference)\n- Liu et al., [*Image Watermarks are Removable Using Controllable Regeneration from Clean Noise*](https:\u002F\u002Farxiv.org\u002Fabs\u002F2410.05470) (ICLR 2025) — the pixel-regeneration method the optional CtrlRegen backend implements — [code](https:\u002F\u002Fgithub.com\u002Fyepengliu\u002FCtrlRegen)\n- Kassis & Hengartner, [*UnMarker: A Universal Attack on Defensive Image Watermarking*](https:\u002F\u002Farxiv.org\u002Fabs\u002F2405.08363) (arXiv:2405.08363; IEEE S&P 2025) — a universal watermark attack compared on a different metric than CtrlRegen\n- Goonatilake & Ateniese, [*Removing the Watermark Is Not Enough: Forensic Stealth in Generative-AI Watermark Removal*](https:\u002F\u002Farxiv.org\u002Fabs\u002F2605.09203) (arXiv:2605.09203) — motivates the conservative-strength default: removal can still leave forensic traces\n- [mertizci\u002Fnoai-watermark](https:\u002F\u002Fgithub.com\u002Fmertizci\u002Fnoai-watermark) (CLI\u002FPython toolkit for SynthID\u002FStableSignature\u002FTreeRing removal and AI metadata stripping)\n- [0xROOTPLS\u002FDeSynth](https:\u002F\u002Fgithub.com\u002F0xROOTPLS\u002FDeSynth) (SynthID removal for OpenAI\u002FGoogle images)\n- Institute of AI PM, [*AI Content Provenance and Watermarking: The PM's Guide to C2PA and SynthID*](https:\u002F\u002Fwww.institutepm.com\u002Fknowledge-hub\u002Fai-content-provenance-watermarking) (two-layer industry model: C2PA + imperceptible watermark \u002F soft binding; SB 942 \u002F EU AI Act Art. 50 context)\n","这是一个用于清除多厂商AI生成内容水印与溯源标记的开源工具集。它支持三类清理：A层（不可见Unicode字符、双向控制符等文本隐式标记）、B层（基于统计特征的文本水印，通过重写钩子处理）、文件层（C2PA、EXIF、XMP、文档属性等元数据），覆盖PNG\u002FJPEG\u002FSVG\u002FPDF\u002FDOCX\u002FHTML\u002FMarkdown等多种格式。项目纯Python实现（3.10+标准库），无需深度学习依赖，可作为Agent技能或独立脚本调用，适用于内容创作者、研究人员及隐私敏感用户对自有AI生成内容进行合规性清洗与发布前净化。",2,"2026-08-13 02:30:05","CREATED_QUERY"]