Repository navigation
v2.0.1 — security + robustness fixes from an adversarial review
A security and robustness release following an adversarial review of 2.0.0. No feature changes; one behavioural fix worth noting if you assemble 16-bit spectral images (see Silent data loss below).
Memory safety
- All three image encoders now bound their own input.
encodeTiffBytes/encodePngBytes/encodeJpegBytescomputed the required buffer size in 32-bitsize_t(WASM is 32-bit) with no ceiling, so a caller could wrap the product to a small value, pass the "buffer big enough?" check, and make the scanline and plane loops read past the real allocation. The size is now computed in 64-bit and capped at the same limit the decoders use — restoring the invariant that every WASM entry point bounds its own input rather than trusting its caller. - Truncated PNGs now fail instead of leaking heap. libpng's read callback must deliver exactly the bytes it asks for; ours silently copied fewer on a truncated or crafted file, leaving the tail of libpng's buffer holding whatever was previously on the heap — which could surface in a decoded image or in an extracted embedded profile. It now raises a proper error.
Supply chain
- libxml2 is pinned to a commit, not the mutable tag
v2.12.6. It is the parser that handles untrusted XML during the ICC round-trip, and a git tag can be retargeted upstream. - CI verifies the committed WASM checksums before building. To be clear about what that does and does not prove: it catches corruption and artifact/manifest drift; it is not protection against a malicious commit, which would simply update both.
Resource limits and responsiveness
- Transform Image now bounds its output, not just its input. The destination channel count comes from the chain's last profile, so a legitimate multichannel DeviceLink combined with float output turned a 64 MP image into a ~3.8 GB allocation — then copied again into the WASM heap. Oversized conversions are now refused up front with the actual size and a suggested remedy.
- The transform no longer freezes the tab. The chunked loop ran as one uninterruptible task, so the page locked for the whole conversion and the "Transforming…" state never even painted. It now yields between chunks and shows a live percentage.
- The spectral assembler is bounded the same way (channel count is user-driven, so the total was previously unbounded).
Silent data loss
- 16-bit spectral planes are preserved. The Spectral assembler previously kept only the high byte of each 16-bit sample, discarding half the measured precision of a spectral scan without any indication. An all-16-bit input set now produces a 16-bit TIFF; mixed bit depths reduce to 8-bit explicitly. If you assembled 16-bit spectral data with 2.0.0 or earlier, re-run it.
- Out-of-range results are reported. Transform Image counts samples the CMM pushed outside the representable range and tells you the percentage, instead of silently shipping clipped or black pixels — the way a degenerate profile or a heavily out-of-gamut absolute-colorimetric run would otherwise present as a clean conversion.
Hardening
- The in-app guide's HTML→React converter now enforces a tag allowlist, drops event-handler attributes, and restricts link schemes. Only repo-generated content reaches it, but it is an injection-shaped sink that the repository's pre-commit check structurally cannot detect, so it defends itself.
- The production build no longer trusts
localhost:3001in the cross-app launch handshake. It previously both accepted profile bytes from, and announced itself to, any co-resident process on that port with no confirmation. Development builds are unchanged. - Dropped colour datasets are size-checked before being read into memory, matching how profile loading already worked.
Documentation
Two claims that overstated what the code delivers were corrected: the XML entity-expansion rationale (upstream iccDEV no longer disables libxml2's own guards, so ours is defence in depth rather than the sole control), and an explicit note on what the pre-commit injection check can and cannot catch.
Not changed, and why
A reported "critical heap overflow" in TIFF decoding was investigated and not patched. The 32-bit overflow is real arithmetic, but crafted TIFFs wrapping the value to both zero and a small non-zero value are rejected by libtiff's own overflow guard before any allocation happens. Adding a redundant check on a path libtiff already closes would have been noise.
Known limitations (unchanged from 2.0.0)
- Invert Transform remains hidden pending worker isolation.
- ICC.2 (iccMAX) support is partial — no multi-part ICS workflows, no V5 sub-profile selection.
- Float TIFF source decoding is still unsupported (float output works).