Architecture informed by a primary-source evidence review completed in August 2026. The corrected facts and claims discipline are recorded here so the shipped design does not depend on local, gitignored research notes.
- Verifiable and best-effort results never mix. Unicode counts and removed metadata structures are verifiable. Rewrite/inpainting/detector attacks are probabilistic and labeled as such.
- No “watermark-free” certification. A detector miss is not proof that every signal trace is absent.
- Inspect before clean. Mutation interfaces have corresponding inspection/reporting paths.
- Stdlib core, explicit adapters. Core behavior works on Python 3.10+ without third-party packages. Network/model/GPU/system tools sit behind visible seams.
- No destructive guessing. Visible cleaning requires a user mask, explicit box, or detector adapter. The implementation never guesses “the center must be the watermark.”
- Content you own. See
skills/remove-ai-marks/references/ethics.md.
Each module exposes one small interface; implementation complexity stays local.
| Module | Interface | Hidden implementation / invariant |
|---|---|---|
| Asset routing | asset_kind.classify_asset(path, forced_kind=...) |
Immutable extension catalog, override/extension/content precedence, 4 KiB sniff, text fallback |
| Batch discovery | batch_inputs.select_inputs(...) -> InputSelection |
Source validation, glob/recursive discovery, generated-file skipping, deduplication, excluded output roots, batch determination |
| Single-asset cleaning | clean_asset.clean_asset(path, dest, plan) -> CleanResult |
Immutable per-kind plans, semantic residual state, backup/write ownership; no presentation or exit-code mapping |
| External commands | external_command.run_command(argv, *, timeout=..., output_limit=...) -> CommandResult |
Shell-free argv execution, bounded output tails, one end-to-end deadline, process-group cleanup |
| PNG traversal | png_chunks.iter_png_chunks(data) |
Shared memoryview-backed chunk payloads, strict IHDR/IEND order, length bounds, and CRC validation |
| Layer A | text_unicode.clean_text(text, ...) |
Unicode classification, context-aware semantic preservation, normalization, stats |
| Layer B | rewrite_text.RewritePlan; rewrite_text.rewrite(text, plan) |
Immutable rewrite policy, backend routing, private provider adapters, TSAPA delegation |
| Layer B transport | layer_b_http.request_json(endpoint, route, payload, ...) |
Endpoint/route/timeout validation, path-prefix-safe joining, same-origin redirects, bounded JSON-object decoding, safe transport errors |
| TSAPA engine | tsapa.tsapa(text, llm=..., pll=..., embed=...) |
Chunking, fitness, NSGA-II, crowding, crossover, PLL-guided mutation, knee selection |
| Visible marks | morphomod.remove_visible(path, dest, plan) |
Immutable VisiblePlan; mask I/O, hole fill, O(n) dilation, PNG codec, inpaint adapter, restore |
| Raster metadata | image_meta.clean_image(path, dest, ...) |
PNG/JPEG parsing; delegates ISO-BMFF work to heif_meta.neutralize_heif(...) |
| Containers | container_meta.clean_container(path, dest) |
SVG/PDF/DOCX/ODT/HTML/Markdown format logic |
| Soft-binding risk | inspect_soft_binding.inspect_soft_binding(path) |
Byte scan plus optional C2PA reader; detection only |
A seam exists only where at least two adapters are real:
- LLM rewrite: Ollama and OpenAI-compatible HTTP; offline tests inject a fake callable. Thinking suppression is an explicit Qwen/Transformers-compatible option, never an assumed extension.
- PLL: OpenAI-compatible token logprobs and a labeled heuristic fallback.
- Semantic similarity:
/v1/embeddingsand shingle-Jaccard fallback. - Visible inpainting: stdlib texture-patch (default), nearest-boundary uniform-background fallback, and an external command adapter (LaMa/MI-GAN/diffusion tools).
- Visible localization: user mask/box and an external detector command.
- PDF: exiftool, qpdf structural rewrite (when present), full-document pypdf clone, and unchanged-copy fallback.
- SynthID scoring: unavailable/no-op default and external
reverse-SynthIDcheckout.
The CLI modules orchestrate these interfaces; they do not contain alternate implementations.
| Channel | Layer | Mechanism | Guarantee class | Status |
|---|---|---|---|---|
| Text | A | Hidden Unicode/space cleanup with semantic preservation | Verifiable | Shipped |
| Text | B | Paraphrase/back-translation/structural/TSAPA-style rewrite | Best-effort | Shipped |
| Text | C | Seeded character perturbation | Best-effort; can reduce text integrity | Shipped, opt-in |
| Files | M | C2PA/EXIF/XMP/doc properties | Verifiable per parser | Shipped |
| Images | V | Explicit mask → refine → dilate → inpaint → restore | Region action verifiable; fidelity best-effort | Shipped |
| Images | P | Optional external SynthID score | Maintainer/version scoped | Scoring only |
| Files | S | Soft-binding/remote-manifest inspection | Detection only | Shipped |
| Video | V/M/P | Temporal inpaint, metadata, regeneration | Best-effort | Roadmap |
| — | Training backdoors / soft-binding removal | — | Out of scope | — |
clean_file.py,inspect_file.py, anddemo.pyobtain the processing family throughasset_kind.classify_asset(...).- Explicit override wins; otherwise a known extension wins over conflicting bytes. Unknown suffixes use a 4 KiB prefix for image/container detection, then fall back to text.
- ZIP-based container detection opens the file-backed archive because its central directory is at the end; classification never loads the whole file.
- Source eligibility, presentation, cleaning, and reporting remain caller-owned.
- ZWSP/tag-character/orphan controls remain removable.
- Contextual ZWJ/ZWNJ, variation selectors, invisible math operators, and balanced bidi controls are preserved by default because removal can change rendering or meaning.
--strip-semantic-formatis an explicit aggressive mode.- Character perturbation runs last because Layer A intentionally reverses its safe modes.
- TSAPA requested through
clean_file.pyrequires a live configured backend; it never writes an operator prompt into the output file or silently falls back.
- PNG/JPEG critical pixel data is never removed because a payload contains marker-like bytes.
- HEIF/AVIF cleaning preserves file length and item offsets: matching JUMBF/C2PA UUID boxes become
free; targeted item bytes are overwritten at equal length. Unsupported external/idat metadata extents fail closed. - Camera/editor EXIF is preserved when
--keep-non-ai-metadatais selected. - PDFs are never byte-deleted without rebuilding cross-reference/object offsets; absent a structural cleaner, input is copied unchanged and reported residual.
- Encrypted PDFs are never regex-edited.
- pypdf clones the complete document graph, writes to memory first, then publishes a complete parseable result.
- DOCX
customXmlparts are dropped and dangling relationships / Content-Type overrides are pruned: leftover customXml can re-carry provenance data, and pruning keeps the package valid.
- Dilation reads the original mask—not a mask being mutated in place—so it cannot cascade into a frame-wide flood.
- A completely masked image is rejected: no surrounding context exists for inpainting.
- The stdlib codec accepts only non-interlaced 8-bit gray/RGB/RGBA PNG and fails closed otherwise.
- External commands run with
shell=False, have timeouts, and must produce their declared output within the encoded-size limit. - Original pixels outside the refined mask are restored exactly for PNG.
- Directory outputs preserve relative paths; every input/output/mask alias and collision is rejected before the first write.
- Directory roots are namespaced when multiple roots are provided; explicit-file basename collisions fail before any write.
.cleaned.*,.mask.*, and.bakartifacts are not reprocessed; in-place backups use exclusive no-follow creation.- A directory is batch mode even if a glob returns one file.
- Exit
0means all requested operations completed without retained requested risk;1means processing error or residual signal;2means usage/input selection error.
- Finding confidence is one of
confirmed,probable,informational,likely_false_positive— shared by the inspectors and the audit suite. - SARIF 2.1.0 export (
wm-audit-dir,wm-audit-site): rulesAI-WATERMARK-C2PA(error),AI-WATERMARK-METADATA(warning),AI-WATERMARK-UNICODE-LAYER-A(warning),AI-STYLES-HIGH-PROBABILITY(note); URIs are relative via%SRCROOT%; driver namewatermark-remover. - Audit exit codes:
0no actionable findings,1actionable findings,2usage/refusal error,3partial scan (inconclusive — some inputs could not be scanned).
chunk → diverse population → evaluate → NSGA-II reduce
→ sentence crossover → PLL-guided mutation → repeat
→ Pareto knee point
Attack fitness:
f_atk = w1·PLL + w2·ngram_diversity + w3·lexical_diversity
Fidelity is embedding cosine similarity when available, otherwise a labeled shingle-Jaccard proxy. The HTTP PLL adapter consumes token logprobs from /v1/completions; failures fall back to heuristic_pll. Literature ASR/BERTScore figures describe the paper's experiments—not this run.
RewritePlan.prompt(...) creates an offline-only plan for the demo;
RewritePlan.live_tsapa_from_environment(...) resolves the unified cleaner's live
configuration. The standalone CLI builds the same frozen plan from its flags.
rewrite(text, plan) is the sole execution interface.
Generation, PLL, and embedding requests share the Layer B transport seam. Provider adapters still own request payloads, authentication policy, and response fields; TSAPA owns fallback labels and degradation counts.
explicit/external localization
→ fill enclosed mask holes
→ square-kernel dilation (d=3 default)
→ stdlib or external inpaint
→ restore original outside mask
→ metadata clean (when invoked through clean_file.py)
The default stdlib backend selects a nearby texture patch by boundary error and fully replaces the dilated mask; optional module-level feathering is explicit. Dogfood tests cover flat and textured backgrounds. Nearest-boundary wavefront fill remains available as simple for uniform backgrounds. Production-grade semantic reconstruction still belongs in an external LaMa/MI-GAN/diffusion adapter. No model weights or research-licensed code are vendored.
- C2PA Manifest Stores use JUMBF/BMFF structures, claims/assertions, and COSE signatures; assertions may contain JSON/JSON-LD, CBOR, or embedded content.
- JPEG embeds through APP11/JUMBF; PNG uses private ancillary
caBX, not generictEXt. - Inserting bytes is easy; creating a valid, trusted, correctly bound Content Credential is not.
- Hard-bound metadata stripping does not remove pixel marks, fingerprints, remote manifests, or soft bindings.
- The NeurIPS 2024 paper is Invisible Image Watermarks Are Provably Removable Using Generative AI; Erasing the Invisible is a separate competition.
- Deep Image Prior removal is 2025; CtrlRegen is ICLR 2025; NFPA is The Future Unmarked (NeurIPS 2025).
reverse-SynthIDcarrier models and figures are maintainer-reported reverse engineering, not published Google internals or independent proof.- Unicode sanitization is distinct from token-distribution watermarking such as SynthID-Text.
C2PAremoveris Go/WASM, not a Python package;noai-watermarkdoes not document visible-mark removal.
| Format | Behavior |
|---|---|
| PNG | C2PA/text/XMP/EXIF chunks; stdlib visible pipeline |
| JPEG | APP/JUMBF metadata; external visible backend |
| WebP/BMP/GIF/TIFF | Format-aware metadata rewrite (RIFF chunks, trailing bytes, app extensions, IFD patching) |
| HEIC/HEIF/AVIF | ISO-BMFF brands, JUMBF boxes, top-level XMP uuid, Exif/XMP item extents |
| SVG/PDF/DOCX/XLSX/PPTX/EPUB/ODT | Format-aware metadata rewrite (+ Layer A on inline text) |
| HTML/Markdown | Provenance metadata + Layer A text |
| Text/code | Layer A, Layer B, character perturbation |
- Video metadata without transcoding.
- Video visible removal with optical-flow temporal consistency.
- Spectral subtraction via reverse-SynthID V4 remains research-only; shipped pixel work is DCT band suppression (--remove-synthid) plus external regeneration (--remove-pixel ctrlregen|diffusion), all best-effort.
- Ground-truth-aware visible-removal metrics when paired originals exist.
- Audio watermark detection/removal research.
Shipped since the 0.4.0 port: the HTTP service (wm-serve + OpenAPI), the audit suite (wm-audit-dir, wm-audit-site, SARIF export), zero-LLM stylometry, vendor/harness text detectors, and the MarkLLM/CtrlRegen/MarkDiffusion heavy-backend adapters.
Pixel-watermark removal guarantees, C2PA soft-binding removal, training backdoors, secret-key detector emulation, “proven human-written” claims, and any universal detector-failure certification. Detection/warning for soft binding is shipped; removal is not.