v0.3.0 — upstream sync and a browser-side keyed-Gumbel detector
Two things: the ports caught up with upstream guillaumemeyer/watermarks-remover, and the Inspector gained its first statistical detector that actually works on this hosted page.
Keyed-Gumbel (EXP), in the browser
js/gumbel.js, a port of upstream's detect_gumbel.py. No sidecar, no model weights, no network: the whole test is HMAC arithmetic over the text, so it reaches a verdict here on GitHub Pages where every other statistical row can only say unavailable. Open Keyed-Gumbel (EXP) key on the Inspector tab and paste the key the text was generated with.
SHA-256 and HMAC-SHA256 are implemented in the module rather than taken from crypto.subtle, which is async, needs a secure context, and would cost roughly two thousand importKey + sign round trips on a 1000-word input. Token ids are BigInt and the uniform is computed as Number(id) + 0.5 before the division, matching how CPython evaluates the same expression; that ordering is what makes the statistic reproduce bit for bit.
Read the result the way upstream intends it. This is a same-key replay, valid only against the key, tokenizer and PRF layout used at generation, so a clean verdict means not this key and never no watermark. The PRF layout is a clean-room instantiation, auditable but not bit-compatible with any particular engine's kernel. The key is held in the page for the length of the run and is never stored, transmitted, or written into the JSON report.
Layer A hardening (upstream #133)
U+180F(Mongolian FVS4, assigned in Unicode 14),U+3164andU+FFA0(Hangul fillers) join the strip set and their script-glue sets: kept next to their own script, stripped when they float.- Unicode noncharacters and reserved default-ignorable code points become strip-class, with
noncharacterandreserved_ignorableinspect kinds. - Visible-layout format controls are kept next to their own script: Egyptian hieroglyph quadrat, Duployan shorthand, musical beaming.
Image containers (upstream #176, #182, #183)
- A dropped ISOBMFF box is overwritten with an equal-size
freebox instead of being spliced out, so absolute media offsets stay valid. A cleaned AVIF or HEIC now keeps its original length; the metadata is still gone, thefreepayload is zeroed. - A truncated PNG chunk or ISOBMFF box keeps its tail, copied through verbatim and reported as an action, instead of being dropped while the run claims the file was already clean.
inspectIsobmffruns the whole-file C2PA byte scan even when no box parses, which is exactly when it matters most.
Upstream #156 is deliberately not mirrored: it hardens error handling around the external c2patool subprocess, and a browser has neither. Recorded in scripts/upstream-sources.json.
Parity anchors
service/scripts/text_unicode.py=ab0197b06263ad83a489032be69103e9a8725c72ddc3cf002a260c87b02647acservice/scripts/image_meta.py=78e5a67db243a4eac2a97b97cea036faae448bdaa2d77c59182af2c972657794service/scripts/score_stylometry.py=cd3dae134d5f641120218fd525afd523d4a0bfb52d4d73c0e92dc04e3bcc6d59(unchanged)service/scripts/detect_gumbel.py=f908272084cd6783a2daa015d51cd1348f56e4bb45fe69168bd85f57b62cd4c1(new)
476 parity tests against upstream main. New in this release: 83 keyed-Gumbel cases, including one that forges a winner sequence and asserts it lights up under the generation key and falls back to chance under another, and an i18n test that every locale carries every key and every data-i18n attribute resolves.
This repository still does not contain the upstream server; it is a client plus an optional local sidecar.