Twenty-one decisions shaped Archx. Each one below states the problem,
what we chose, what we rejected and why, the honest pros and cons, and the measured
evidence — with local widgets where a mechanism is easier to feel than to read about.
Nothing here is marketing: where a choice costs something, the cost is printed.
↳
One image, start to finish
Follow a private master through the recommended server-delivery
({ id }) path: permission first, pixels second. Choose a viewer mode,
renderer/extraction overrides, and server options — the moving picture, packets, and stage notes
change with that configuration.
Animated flowchart — master in, protected view out
Illustrated server configuration: the optional server
branches start switched on so every path is visible. Each toggle prints the base middleware default;
switch a branch off to see what that deployment would skip. The tile animation uses the page’s real
plan-v2 builder, but the drawing itself is explanatory — no image, token, or server request leaves
this page. Stage timing is budgeted by visible beats and active branches rather than one fixed
interval: simple holds are short, while gates, scans and transforms get sequential time; each stage’s
viewing budget is printed with its step below. Those budgets are reading time, not server runtimes.
Viewer mode
Renderer flag
onExtract
Server options in this illustrated delivery
One-time tokens are on by default in the middleware; this
illustrated token is also sender-constrained to the browser’s proof key.
Stage 1 of 10
SERVER · PRIVATE
1 · The master goes in — and stays there
The original is indexed by image ID in a masters directory outside every
public root declared to Archx. Startup refuses overlap with declared roots; undeclared host mounts remain the operator’s responsibility. No Archx route returns
this file.
Shield — the default viewer
Shield uses Canvas 2D when the renderer is left on auto. A private working canvas
unscrambles the entitled derivative at backing-store size, bakes any configured client watermark and
per-session forensic blocks, paints the visible canvas, then zeroes the working canvas. Instance-level
readback returns the selected decoy; a prototype-call readback additionally needs an origin-tainted
canvas (or promotion to WebGL) to be browser-blocked.
Effective renderer
Canvas 2D (auto from Shield)
What leaves the server
AES-256-GCM-encrypted .archx scrambled derivative · no master
Programmatic readback
Black branded decoy from instance readback APIs
Scope: this chart follows token delivery. Precomputed
{ assetId } delivery is deliberately different — derivatives are scrambled at
build time and selected from a manifest on static/CDN hosting, so there is no per-view authorize,
mint, redemption or Sharp stage. Plain URL/Blob sources likewise skip the server boundary and must
not be described as if they had one.
Preset names are bundles over this same flow
Presets do not create separate pipelines. They choose a base mode and pre-fill ordinary flags;
any flag can still be overridden when the preset object is spread.
Original-quality request profile with the default Canvas path unless a renderer flag says otherwise.
commerce / marketplace
Vault
WebGL path and capture shield; marketplace also enables batched mints for grids.
performance
Shield
Forces Canvas, catalog sampling, batched mints and smaller bitmap-cache budgets.
maxProtection
Vault
Forces WebGL, requires proof-of-possession, and enables the 60 Hz balanced temporal interlace.
screenshotResistant
Fortress
Adds the pointer Lens to Fortress’s temporal/capture/strike defaults; still not screenshot prevention.
A
Delivery — what leaves the server
D-01
Masters stay outside declared public roots; overlap checks fail closed
SecurityFail-closed
The original file should not be web-addressable; the middleware is the intended reader, and it refuses overlap with public roots declared in its configuration.
The problem
Most “protected” galleries still host the original at a guessable URL. One directory listing, backup file, or misconfigured static route hands over the master — every other layer is then decoration.
We chose
Originals should sit in a directory the web server never serves. The middleware resolves real paths, rejects symlinks that escape, and throws at startup if a masters directory overlaps any public directory declared to Archx; undeclared host mounts remain the operator’s responsibility. The preflight audit mirrors the same rule (a v1.21 red-team find: the audit once said “ok” where startup refused).
Alternatives rejected
Signed URLs to public files — the file is still fetchable by anyone who obtains or guesses the URL class; revocation is per-URL theatre.
Obscure filenames — secrecy by entropy of a path leaks via logs, referrers, and screenshots.
Pros
Whole classes of static-file bugs become irrelevant.
One auditable read path for every byte that leaves.
Cons / costs
Requires a server component (or the offline asset builder) — pure static hosting of originals is out.
Deployments must think about filesystem layout once.
Evidence
Red-team traversal/symlink/config attacks (suite sections C–D) all held; the repository demo deliberately 404s /demo-masters/… when run locally, so anyone can test it. RESEARCH §2.29.
D-02
Entitled derivative width is capped; preview-only needs an explicit policy
SecurityQuality
The server resizes to the signed, entitled width before anything is scrambled or sent. That width is not inherently the browser’s physical display size: maxWidthFraction defaults to 1, so an entitled viewer can receive native-width pixels when the other caps permit.
The problem
If full-resolution pixels reach the browser “for fast zoom”, extraction gets full resolution too. Client-side downscaling is a polite request, not a boundary.
We chose
The global maxWidthCap and optional authorize maximum constrain each viewer’s requested width; maxWidthFraction < 1 additionally caps it relative to the master. The server renders the resulting signed width (optionally snapped to canonical buckets), and the token binds that width. If a fraction cap falls below the supported 64 px minimum, mint or redemption is denied rather than rounding the cap up. Smuggling a bigger w at redeem time changes nothing.
Alternatives rejected
Full-res + CSS scaling — the extracted copy is print-ready.
Deep-zoom tile streaming (IIIF-style) — excellent pattern, documented as the escalation path, but a much larger surface; deferred deliberately.
Pros
With maxWidthFraction < 1, even a perfect reconstruction stays below native width.
Smaller payloads; two-stage derivative caching keeps it cheap.
Cons / costs
No infinite pinch-zoom into print detail (the viewer offers a lens within the derivative).
Preview-only width policy is a real product decision the host must make; the default fraction of 1 allows native-width pixels when other caps permit.
Evidence
Entitlement-escalation attacks (mint/redeem width smuggling, absurd widths) held in the red-team suite; derivative width is asserted from the container header in tests.
D-03
Scramble is obfuscation; AES-256-GCM is the secrecy — labelled as such
SecurityHonesty
Tile permutation makes captured payloads harder for casual tools to inspect; it is not encryption and can be reconstructed. When wire confidentiality matters, the payload can also be sealed with AES-256-GCM.
The problem
Vendors routinely call pixel shuffling “encryption”. A permutation can be attacked with jigsaw solvers and known-plaintext edges; overselling it leaves buyers with a false threat model.
We chose
Two separately-named layers: a key-seeded tile permutation + mirrors (obfuscation that adds friction to casual tools and raw network inspection), and optional AES-256-GCM over the encoded derivative (authenticated wire confidentiality against parties without the key; the authorized browser receives that key). Docs state plainly which is which.
Alternatives rejected
Scramble-only marketed as encryption — dishonest and weaker against solvers.
Encryption-only — protects the wire, but an authorized browser still receives clear pixels; it does not stop in-page extraction or screenshots.
Pros
Each layer is evaluated against the right attacker.
Operators can trade CPU for secrecy explicitly.
Cons / costs
Two mechanisms to configure and explain.
AES path needs key handling in the client loader.
Evidence
Plan derivation and padding went through five measured generations against seam-oracle attacks (D-05). Crypto core is pinned to node:crypto test vectors in the unit suite.
D-04
128-bit-seeded scramble plans (v2); the 32-bit v1 kept frozen, not deleted
SecurityCompatibility
Version-1 plans derived from a 32-bit seed — brute-forceable offline. v2 seeds sfc32 from a 128-bit cyrb128 hash. Old payloads still decode because v1 was frozen instead of “fixed”.
The problem
With a 32-bit seed, an attacker who captures one scrambled payload can enumerate every seed, unscramble, and score the results — recovering the permutation without the key. Measured: feasible on a laptop.
We chose
New payloads default to plan v2 (128-bit state, warm-up rounds). v1 remains byte-for-byte for decoding legacy assets; no public entry point emits it unless explicitly requested. Try the real algorithm:
Alternatives rejected
Silently change v1 derivation — orphans every previously built asset.
Bigger grid instead of bigger seed — permutation size was never the weak point; seed entropy was.
Pros
Removes the direct 32-bit seed-enumeration weakness; v2 remains deterministic obfuscation, not cryptographic confidentiality.
Zero migration for existing assets.
Cons / costs
Two derivations to maintain and test forever (accepted: formats are forever).
Widget — the real scramble plan, in your browser
Hover a tile…
This runs the shipped buildScramblePlan code (cyrb128+sfc32 / mulberry32, ported verbatim and parity-tested against the library). Left = original tiles, right = where each lands on the wire; ⇄/⇅ marks are mirrors.
Evidence
RESEARCH §§2.18 and 2.22 (plan-v2 introduction and default migration); golden tests pin both derivations. The widget’s port is diff-tested against src/core/plan.js outputs.
D-05
Padding that does not betray the edges (five generations)
SecurityQuality
Images rarely divide by the grid; the padded strip sounded innocent until it kept naming the boundary tiles to solvers. Final design: a keyed, DC-anchored coherent wrap.
The problem
Black padding (v0) identified every edge tile and its flip. Edge replication duplicated rows exactly. Reflection left a palindromic seam (row-diff MAD ≈0.6 vs ≈17 natural). A shifted wrap broke the palindrome but re-phased rows and left a luminance cliff — each generation was measured, then broken by a better statistic.
We chose
One keyed toroidal offset for the whole strip (texture stays coherent), chosen by keyed best-match search; each pad pixel DC-shifted by the ±16px local box-mean delta (no seam cliff), feathered toward the true edge, plus block-constant non-zero keyed noise so pads never equal the edge on flats.
Alternatives rejected
Black / replicate / reflect / naive wrap — each broken by a named measurement, above.
Crop-to-grid — throws away delivered pixels; display must stay whole.
Pros
Strip statistics now resemble ordinary texture continuation.
Unpredictable without the key; irrelevant with it.
Cons / costs
The most intricate 200 lines in the scramble path; display never shows them (cropped before presentation), but they must stay bit-frozen.
Evidence
The reflection-padding seam measurement and follow-up are in RESEARCH §2.20; fused pad+scramble output is equality-tested against the two-step form.
B
Tokens & access control
D-06
One-time HMAC tokens with an allowlisted claim set — not a JWT library
Security
A token is base64url(claims).base64url(HMAC-SHA256): eight-ish claims, every one validated (audience, scope, image, width, iat/exp window, nonce), normally spent once while the claim record is retained; the evict-on-saturation policy can reopen a narrow replay window under sustained pressure (D-07), canonical encodings only.
The problem
General JWT stacks bring algorithm negotiation, remote key fetching, and claim liberalism — the exact surface behind “alg:none”, kid-confusion, and claim-smuggling bugs. This system needs one narrow thing: “viewer V may fetch image I at width W, once, soon.”
We chose
A fixed HMAC construction, a frozen claim allowlist (unknown keys refuse), strict types, TTL and clock-skew windows, and a one-time claim store keyed canonically. v1.21 added canonical-encoding rejection after the red team showed base64url slack bits let four strings carry one MAC. Single-secret verification authenticates before parsing.
Alternatives rejected
Off-the-shelf JWT — larger parser/alg surface for zero needed features.
Opaque session ids only — loses stateless width/image binding and forces a lookup per request.
Pros
Tiny, auditable verifier; timing-safe compare; replay is denied while the spend record is retained; under pressure, evict mode can reopen a narrow window, while strict mode refuses new redemptions.
Key rotation via keyring kid without alg games.
Cons / costs
Not interoperable with JWT tooling (by design).
We own the verifier’s correctness — hence the fuzz battery.
Widget — mint, inspect, and tamper with a real token
Press “Mint token”. Runs 100% locally with WebCrypto; the example secret never leaves this page and no Archx server is contacted.
The local widget checks canonical encoding, HMAC, and core v2 claims (using the default 1920 px width cap); optional-claim value checks are simplified, so this is a teaching model rather than the server validator. The padding variant has a valid MAC but a non-canonical string — watch it fail anyway.
Evidence
Red-team sections B + H: corrupted/tampered/wrong-secret/cross-image/replayed/expired/fuzzed-claim/kid-confused tokens all refused; a failed forgery does not burn the genuine claim; 16-way race admits exactly one redemption.
D-07
Replay-store saturation is an operator policy, not a surprise
SecurityPerformance
A one-time-claim store is finite. When it fills, something must give: evict the oldest claims (a replay window re-opens) or refuse new views (strict). We ship both, default by deployment profile, plus the sizing math.
The problem
An attacker who can force churn (many cheap mints) can push legitimate claims out of a bounded store — converting a memory bound into a replay vulnerability, silently.
We chose
Explicit saturation: 'evict' | 'strict'. Library default is evict (availability), the hardened production preset is strict (refuse new redemptions rather than forget a spend). Sizing rule: maxEntries ≥ peak redemptions/sec × TTL × margin.
Alternatives rejected
Unbounded store — trades a replay bug for an OOM kill switch anyone can press.
Silent eviction (the industry default) — the failure mode exists but is undocumented and unmeasured.
Pros
The trade-off is named, configurable, and auditable in config review.
Strict mode makes saturation a loud 429/503, not a quiet replay.
Cons / costs
Operators must actually size the store (the widget below helps).
Strict mode can refuse real users under attack — that is the point, but it is a real cost.
Widget — size your claim store
Evidence
RESEARCH §2.28 (critique response) + OPERATIONS sizing table; saturation behavior is unit-tested in both modes.
D-08
Sender-constraining: bind tokens to the browser that asked
SecurityCompatibility
A copied token should be worthless elsewhere: optional User-Agent binding for cheap friction, and DPoP-style proof-of-possession (RFC 9449 shape) with an ephemeral in-page key for real binding.
The problem
Bearer tokens leak via logs, screenshots of DevTools, and shared links. One-time use narrows the window but a fast thief can spend first.
We chose
Two strengths: UA binding (friction, trivially spoofable by the thief who copies headers too — labelled as such) and proof-of-possession: the page holds a non-extractable P-256 key; the token carries its JWK thumbprint (jkt); redemption needs a fresh signed proof bound to method, URL, token hash, and a one-time jti.
Alternatives rejected
IP binding — breaks mobile/NAT users, trivially shared by co-located thieves.
Cookies-only — the loader is cross-origin friendly; cookies reintroduce CSRF considerations.
Pros
Stolen token + no private key = 403, even inside the TTL.
Proof replay itself is one-time-use.
Cons / costs
Needs WebCrypto in the page; more moving parts; required mode refuses keyless clients outright.
Script already running in the victim page can still ask the page to sign — stated in the docs, not hidden.
Evidence
Red-team PoP battery: bearer copy 403, wrong key 403, wrong target 403, proof replay 403, fresh proof 200. RESEARCH §2.26.
D-09
Deny-by-default authorization; the audit must never bless what startup refuses
Security
On the server, no authorize callback and no explicit allowAnonymous:true mode ⇒ the constructor throws. An explicit denial returns 403; an authorize exception returns a generic 500, and a callback timeout returns 503. Both failures deny delivery and emit a separate archx.authorization_error event. Unknown server-option keys and weak secrets also fail at startup.
The problem
The most dangerous default in access-control code is the permissive one reached by omission: a forgotten callback, a typo’d option, an exception path that falls through to “serve”.
We chose
Fail-closed server configuration: missing both authorization and an explicit anonymous mode is a startup error; authorize results are normalized strictly (garbage shapes ⇒ deny or clamp, never widen); unknown server-option keys are rejected so allowdOrigins can’t silently no-op; the config audit mirrors every startup refusal it can check statically.
Alternatives rejected
Permissive demo default — demos become production; the incident writes itself.
Warnings instead of throws — nobody reads warnings at 2 a.m. during a deploy.
Pros
Misconfiguration fails loudly at boot, in CI, before traffic.
Cons / costs
First-run friction: you must write one authorize function (examples provided).
Evidence
Red-team section C + H (garbage authorize shapes: true/string/null/NaN/negative — denied or clamped, never widened). RESEARCH §2.25–2.29.
C
The viewer — where pixels become visible
D-10
Canvas viewer with poisoned readback — no <img>, no right-click theatre
SecurityHonesty
The picture exists as canvas paint operations, not as an addressable image element. Readback APIs get decoys or black; “disable right-click” is available as a flag but never presented as a control.
The problem
An <img src> is a public handle: save-as, drag-out, DevTools “open in new tab”, extension harvesters. Blocking context menus stops none of those and annoys paying users.
We chose
Canvas2D/WebGL presentation of reassembled tiles; origin-tainted or black readback for toDataURL/captureStream paths where the platform allows; decoy responses for known extraction probes; print paths degraded. Every guard is an individual flag so sites choose their own friction.
Alternatives rejected
Right-click blockers as the headline — theatre (see Overview §01).
EME/DRM — real output protection exists there, but licensing, per-browser CDM deals, and no support for still-image workflows put it outside an open-source JS library. Named honestly as the “choose something else” path.
Pros
There is no direct image-file handle; authenticated scraping still depends on the host’s mint policy and quotas.
Pixel-exact 1:1 display is preserved (MAD 0 in browser tests).
Cons / costs
Screenshots by privileged tooling still work — degraded and attributable, never “prevented”.
Canvas breaks some a11y/save workflows; hosts keep alt-text duties (documented).
Evidence
129-check real-Chromium suite (`tests/browser-test.mjs`): encapsulation, decoy readback, black WebGL readback, replay, guards, and current lifecycle/integrity regressions.
D-11
Temporal interlace at 60 Hz: never show the whole picture in one instant
SecurityQuality
Alternating complementary subsets (strips/blocks/checkerboards) at display rate: the eye integrates a complete image; a single-frame capture inherits holes.
The problem
Any static framebuffer can be captured in one shot. If the complete image never exists in a single presented frame, single captures degrade — and cross-frame averaging fights deliberate pattern choices.
We chose
GPU-shader or pre-rendered alternating frames, configurable pattern (strip/pixel/block), subset size, and target rate (60 Hz default). Fully disable-able; quality impact is measured, not asserted.
Alternatives rejected
Animated watermark overlays only — composited over a complete static frame; capture then inpaint.
Always-on interlace — photosensitive users and some displays suffer; hence flags + reduced-motion respect.
Pros
Single-shot screenshots arrive degraded by construction.
Cheap: shader mask or two pre-renders, no per-frame CPU.
Cons / costs
Determined multi-frame capture + averaging partially recovers; camera photos of the screen integrate like the eye does. Both stated plainly.
Evidence
Browser suite covers shader/frames patterns + capture traces; the repository demo exposes every interlace flag when run locally.
D
Watermarking & detection — attribution after the leak
D-12
Classical spread-spectrum watermarking in pure JS (not neural, not LSB)
QualitySecurity
StegoMark signs each grid cell with a keyed ± pattern, amplitude masked by local texture. Robust profile PSNR ≈28–30 dB buys crop/scale/geometry survival that prettier marks don’t have.
The problem
A leak arrives cropped, resized, re-compressed, screenshotted, sometimes mirrored. The watermark must be findable blind (no original), keyed (only the owner can read/forge), and cheap enough to embed per-viewer at request time in JavaScript.
We chose
Cell-based spread spectrum with keyed polarity/segment signs, texture-adaptive amplitude (flat cells gentle, textured cells stronger), repetition coding (v1–v3) or BCH (v4, D-13). Detection integrates cell evidence over a luma integral image and searches geometry.
Alternatives rejected
Neural marks (SynthID/StegaStamp class) — better PSNR (~40 dB class), but need trained encoder/decoder networks; not feasible in a dependency-free JS library, and regeneration attacks kill them too (measured in the literature at ~0–13% decode).
LSB steganography — dies at the first JPEG re-save.
DCT-QIM with JND masking — a real path to ~38–41 dB, but it is new embedding physics (a future layout version), not a patch to shipped marks.
Pros
Runs anywhere JS runs; opt-in embedding adds CPU work that varies with image size and configuration.
Survives the fixed transform matrix shown below; this is not a screenshot/camera-capture or arbitrary-edit guarantee.
Cons / costs
28.6 dB-class distortion on the robust profile is visible under pixel-peeping — the price of the envelope, printed here rather than hidden.
Diffusion regeneration and 1-bit binarization remain outside the envelope.
Widget — recorded survival (1.30.2 StegoMark run, strength 6)
9 images × 11 fixed transform cases (JPEG q80/q50, resize, center/corner crop+resize, 90° rotation, flop, transpose, brightness+JPEG; no screenshot/camera captures). Robust: 85/99; standard: 63/99. False positives: 0/369 wrong-key checks (one-sided 95% upper bound ≈0.81%, not proof of zero risk). Detector budgets and host details are in the evidence artifact. Re-run: npm run bench:stego:evidence.
Evidence
docs/RESEARCH.md §2.40 and docs/evidence/stego-bench.json record corpus, transforms, detector budgets, per-call timing percentiles, timeouts, and the finite wrong-key confidence bound. “Periodic” synthetic content measured 0/11; it is an adversarial stress host, not a proxy for all text.
D-13
Layout 4 = BCH(188,64) — shipped opt-in, deliberately not the default
QualityCompatibility
Algebraic error correction beats repetition — in quality (+1.4 dB photos) and theory. It lost the pre-registered flip test by one case (84 vs 85 robust), so layout 3 stays default. The bar was set before the experiment; the result stands.
The problem
Repetition coding wastes capacity: to fix k erasures you repeat everything. Literature (Frontiers in Physics 2026 ECC comparison) puts BCH + repetition concatenation at the top for watermark extraction robustness, with deterministic correction at low bit reliability.
We chose
Shortened BCH(188,64) over the unchanged v3 cell layout (48-bit fingerprint + keyed MAC-16 inside the codeword), gentler amplitude curve paid for by the coding gain. Layouts 1–3 byte-frozen; v4 is steganography.layoutVersion: 4.
Alternatives rejected
Make v4 the default anyway — the flip bar (“≥ v3 in every class, better PSNR, FP 0”) was written before measuring; v4 trails by one robust-mode case (0.7-scale photo crops inside a 20 s auto-layout budget). Moving goalposts after results is how benchmarks become marketing.
LDPC — wants soft LLRs and longer blocks; a 188-bit channel with hard cell votes favours BCH.
Better PSNR in every class; standard-mode recovery identical (63/99).
Cons / costs
Needs a v1.20+ detector; slightly behind on small-crop photos under tight time budgets.
Evidence
Bench table in the widget above (D-12) and RESEARCH §2.28; golden-hash test proves layouts 1–3 byte-identical across the change.
D-14
A keyed MAC decides attribution — never a CRC, never a “confidence %”
SecurityHonesty
False attribution is the catastrophic failure (accusing an innocent buyer). Every candidate decode must pass a keyed 16-bit MAC and strict mismatch caps; the reported confidence: 0.99 is a label, not a measured probability — and the docs say so.
The problem
Powerful solvers try thousands of candidate codewords. Against a CRC — or a loose error cap — some noise candidate eventually “validates”, manufacturing a fingerprint from an unmarked image. We measured this happening (a wrong MAC-valid fingerprint at a loose cap) during development.
We chose
MAC-16 inside every codeword version, mismatch caps at 0.30×(observed−64) (≈7σ from noise solves), hard clamp 0.34, agreement/evidence tiers. Legacy CRC codewords remain readable only behind an explicit compatibility flag.
Alternatives rejected
CRC tags — unkeyed: anyone can forge, noise can pass.
Wider caps for more recoveries — buys true positives with false accusations; the exchange rate is unacceptable.
Pros
0/369 wrong-key positives in the recorded sweep (one-sided 95% upper bound ≈0.81%; not a zero-risk guarantee).
Forgery requires the stego key, not just the algorithm.
Cons / costs
Some genuinely-marked but heavily damaged copies correctly return “not found” instead of a guess. That is the intended asymmetry.
Evidence
FP sweep bench:stego --fp (369 checks); MAC-lottery incident and cap derivation in RESEARCH §2.28.
D-15
A decode ladder, not a decoder: BM → ML → OSD → ISD → forensic stash
QualityPerformance
Bounded-distance decoding dies at 37 erasures. Real crops erase 69+ bits and flip a few of the survivors. Each rung exists because a measured case killed the previous one.
The problem
Ground-truth capture on a failing crop: 69 erasures + 13 errors, ~4 of them inside whatever basis you solve from. No single classical algorithm covers that inside a 20-second multi-layout budget.
We chose
Berlekamp-Massey first (fast, guaranteed radius), ML erasure solve (any 64 independent clean bits), OSD-1/2 reprocessing, inline information-set decoding on coherent candidates, then deep ISD over the three best distinct-geometry failures stashed during the search — including one bounded pass when the clock runs out. All randomness input-seeded and deterministic.
Alternatives rejected
Unbounded inline search — every unmarked suspect would pay seconds of ISD; screening latency explodes (measured).
Single best-candidate retry — the max-evidence candidate turned out to be the worst ISD case (an error sat inside its top-24 reliability ranks); hence top-3 distinct geometries.
Pros
Crop recovery that bounded-distance decoding cannot express, inside fixed budgets.
Unmarked images exit via cheap screens (430 ms at 2400px).
Cons / costs
The most intricate code in the repo; gate calibration is content-scale dependent and pinned by the bench corpus.
Widget — where each rung can still save the mark (schematic)
Schematic — real recovery depends on error positions, reliability ordering, mismatch caps and the time budget; every candidate must still pass the keyed MAC. The default point (69, 13) is the measured ground truth of a real failing case.
Evidence
RESEARCH §2.28 root-cause table (six measured causes, six fixes); codec-level OSD/ISD unit tests; two-phase detection timing (dihedral screens 13.9 s → 0.6 s class).
A 64-bit DCT pHash (soft binding), seeded forensic block signs, and a collusion-ranking assay support the primary mark. Each is labelled with what it can and cannot prove.
The problem
Attribution in the real world is an investigation: marks get cropped, buyers collude by averaging copies, suspects arrive recompressed by platforms. One signal, however strong, will sometimes be absent — and overstating weak signals is how innocent people get accused.
We chose
Layered, independently-failing evidence: pHash near-duplicate matching, block-sign correlation against a reference, and a collusion assay that ranks candidate recipients by z-score for human review, backed by an issuance log and signed ownership tokens.
Alternatives rejected
Claiming collusion-proof decoding — Tardos’ bound needs ≈2,180 symbols for c=4 at ε=1e-6; the channel carries 96 bits. Blind collusion-proofing is mathematically out of reach here, so we rank instead of accuse.
Single-signal verdicts — pHash alone is spoofable; forensic blocks alone are weak.
Pros
Graceful degradation: absent mark ≠ absent evidence.
Each layer’s strength is documented in its own terms.
Cons / costs
Operators must run an actual investigation workflow (we ship the guide, not a magic button).
Evidence
Collusion assay ranks averaging participants above innocents in tests; two-buyer averaging yields no phantom fingerprint in the red-team suite. RESEARCH §2.26.
E
Platform — running it safely at scale
D-17
Sharp/libvips under hard budgets, one chained pipeline
PerformanceSecurity
Every transform runs as a single libvips pipeline with pixel/channel caps, failOn, sequential reads, and a wall-clock timeout — because the image decoder is the most dangerous code you expose to the internet.
The problem
Image parsers are a historic CVE farm, and naive multi-pass processing (decode → encode → decode) multiplies both risk and seconds. A 60,000×60,000 IHDR claim should cost a header check, not 14 GB.
We chose
Sharp (peer dependency, operator-patched), chained operations, two-stage derivative construction (cacheable clean base → per-viewer finish), orientation baked only when present, output-pixel budgets that include scramble padding, and controlled error mapping for malformed input.
ImageMagick shell-outs — process-spawn attack surface + full in-memory frames.
Pros
Streaming keeps RAM flat; budgets make DoS arithmetic boring.
A recorded 2-vCPU demo-master run measured the 960px catalog q88/effort-2 derivative at ≈46 ms; cold time varies by host, source, format, and cache state. The warm path is cache + tokens.
Cons / costs
Native dependency to keep patched (documented; audit in CI).
Full-res 24 MP scrambled encodes are encoder-bound (~13 s mozjpeg) — sizing guidance, not magic.
Evidence
Optimization bench (npm run bench:opt); pixel-bomb and garbage-master attacks held fast; fuzz suite over parsers. RESEARCH §2.27, §2.30.
D-18
Bounded everything: no attacker-sized data structures
SecurityPerformance
Rate limiters, replay sets, distinct-image trackers, body readers, batch minters, detect queues — every structure an attacker can grow has a hard capacity and a deadline.
The problem
“Just use a Map” is how a token endpoint becomes a memory-exhaustion service. Slow-trickle bodies pin handlers; 2,000-item batches pin CPUs; distinct-ID floods pin trackers.
We chose
Capacity-capped structures with interval cleanup (no per-request scans), /64+/48 IPv6 limiter tiers (rotation-resistant), deadline-bounded body reads, batch item caps, slow-callback timeouts, and a distinct-image scrape quota with deny/challenge actions.
Alternatives rejected
Rely on the host framework’s limits — varies by deployment; the library must be safe behind a bare node:http server (our demo is one).
Per-IP only rate keys — IPv6 rotation walks straight through (measured; hence tiers).
Pros
Worst-case memory/connections are arithmetic, not vibes.
Quotas bound collection and origin work; tune them to the deployment and use shared state when scaling.
Cons / costs
More knobs; multi-instance deployments should use the Redis store for shared truth (shipped, contract-tested against real Redis in CI).
Evidence
Red-team E + H: mint flood 429s, slow-trickle cut at deadline, 4th distinct image 429, 40-way concurrency bounded. Load test ships in-repo.
D-19
Zero runtime dependencies; an honest Node floor split
CompatibilitySecurity
The library installs with no runtime dependency tree (Sharp is a peer). engines ≥20.9 covers the library; the dev browser suite needs Node 22 — because the Node-20-compatible Puppeteer line failed our audit gate.
The problem
Supply-chain risk scales with dependency count, and “supports Node 20” claims often quietly exclude the test tooling. Puppeteer 25 needs Node ≥22.12; Puppeteer 24 would have kept 20 but dragged advisories our CI rejects.
We chose
Measure it, then write it down: pin Puppeteer 24 was evaluated and rejected on its audit chain (basic-ftp/extract-zip). CI encodes the split honestly — library suites on Node 20 and 22, browser suite on 22; .nvmrc says 22 for contributors.
Alternatives rejected
Lower the audit gate — the gate caught a real same-day advisory during the v1.21 ship run.
Bundle a crypto/base64 library — platform primitives suffice; every dep is a future advisory.
Pros
npm audit on the shipped artifact is an empty tree by construction.
Version claims match what CI actually runs.
Cons / costs
Contributors on Node 20 can’t run the browser suite locally (CI still covers them).
Evidence
RESEARCH §2.28 (Puppeteer measurement), §2.29 (follow-redirects advisory caught at ship time, dev-only chain).
F
Process — how the work itself is governed
D-20
Optimizations must be byte-identical — or be reverted
PerformanceCompatibility
Speed work is gated by output-hash oracles (135 cases at 24 MP last round) and a golden freeze of shipped formats. One “optimization” measured slower and was reverted in the same round; its numbers are published too.
The problem
Floating-point reassociation, encoder-flag drift, and cache-sharing “improvements” silently change pixels or payloads — invalidating issued marks, cached derivatives, and every prior benchmark.
We chose
Every performance change ships with before/after hash probes over odd sizes and unaligned buffers, exact-arithmetic justifications (integer-exact, order-preserved, or permutation-only), and isolated median timings. New physics ships as a new format version (D-13), never as a silent change.
Alternatives rejected
“Visually identical” as the bar — watermark decoders read bits, not vibes.
Upgrades are safe for issued assets; regressions are caught by diff, not by users.
Cons / costs
Some textbook optimizations (separable filters, encoder tweaks) are off-limits; wins must be structural, which is slower to find.
Evidence
RESEARCH §2.29–2.30: flips −64%, rotate/transpose −48%, forensic −37% at 24 MP, all hash-identical; rotate-interior-clamp reversal documented with numbers.
D-21
Honest labelling as an engineering constraint — and 0BSD
Honesty
No “screenshot prevention”, no DRM claims, no C2PA conformance, no independent-audit theatre. The license matches the philosophy: BSD Zero Clause — use it any way you like; attribution appreciated, never required.
The problem
Displayed pixels cannot be made un-copyable (the analog hole is physics). Tools that promise otherwise train buyers to under-invest in the layers that work — access control, resolution policy, attribution, and response.
We chose
Every claim in the README/site maps to a shipped, runnable measurement; limits get their own page (Threat model) including when to choose EME DRM, C2PA provenance, or neural watermarking services instead. The 0BSD license removes permission friction for a tool whose job is protecting other people’s work.
Alternatives rejected
Marketing-first claims — they benchmark well in sales decks and fail in incident reports.
Copyleft (GPL) — would keep the code out of the commercial galleries that need it most.
Claiming C2PA — provenance is not protection; the 2.4 spec’s conformance program is young and we do not sign manifests. Named, not implied.
Pros
Operators size their expectations and their other defenses correctly.
Zero license friction for adoption, forks, and audits.
Cons / costs
“We can’t stop screenshots” loses some sales to vendors who lie about it. Accepted.
Evidence
This page. Every number on it is reproducible from the repository: npm run verify · npm run redteam · npm run bench:stego · npm run bench:opt.