Skip to content

fix(js): close JS/WASM surface findings M2, M3, L1#61

Merged
g4bri3lDev merged 1 commit into
mainfrom
fix/js-surface-consistency
Jul 21, 2026
Merged

fix(js): close JS/WASM surface findings M2, M3, L1#61
g4bri3lDev merged 1 commit into
mainfrom
fix/js-surface-consistency

Conversation

@g4bri3lDev

Copy link
Copy Markdown
Member

Summary

Closes three audit findings on the JavaScript/WASM surface (Task 4 of the audit-remediation plan):

  • M2 — the mode matrix was fake. dithering.test.ts passed mode as ditherImage's third positional argument (DitherOptions, an object), so destructuring a Number yielded undefined for every field and all nine "produces valid output for mode %s" cases silently ran BURKES. Fixed the call to { mode }. Strengthened the per-mode test to diff each mode's output against DitherMode.NONE (so a broken kernel for that mode fails that mode's case) and added a cross-mode "not all identical" guard against future hollowing.
  • M3 — wasm sentinel misfired. wasm/src/lib.rs's dither_image took scheme_id: u8; with non-empty palette_bytes, any parsable scheme_id silently enabled canonical pinning, so a direct wasm consumer leaving it at 0 got Mono-canonical pinning unasked. Changed the parameter to Option<u8>, made an unparsable Some(id) a hard error instead of a silent fallthrough, and dropped the 255 sentinel in core.ts in favor of undefined. This changes the wasm dither_image export's signature (not a breaking change for the TypeScript public API — ditherImage's TS surface is unchanged).
  • L1 — unvalidated dimensions. core.ts returned the caller's claimed width/height while wasm derives height from buffer length, so a short buffer silently produced indices.length !== width * height. core.ts now validates data.length === width * height * 4 and throws, matching Python's existing cross-check style.

Test plan

  • cd packages/javascript && bun run type-check — passes
  • cd packages/javascript && bun run test — 44/44 pass
  • cd packages/javascript && bun run build — passes
  • cd packages/rust && cargo test --workspace — 61/61 pass (58 unit + 3 regression fixtures, untouched)
  • cd packages/rust && cargo clippy --workspace -- -D warnings — clean
  • python3 scripts/check_enum_parity.py — exits 0
  • M2 proof: temporarily zeroed the FLOYD_STEINBERG kernel weights in algorithms.rs, rebuilt wasm — the produces valid output for mode 3 case (and the pre-existing serpentine test) failed; reverted, rebuilt, confirmed green and git status clean on that file.
  • M3 proof: new test constructs a measured palette with an invalid scheme: 99 and asserts ditherImage throws instead of silently dithering without canonical pinning.
  • L1 proof: new test passes an ImageBuffer whose data is shorter than width * height * 4 and asserts ditherImage throws.

M2: dithering.test.ts's per-mode matrix passed `mode` as ditherImage's
third positional argument, which is `DitherOptions` (an object), so
destructuring a number silently yielded `undefined` for every field
and all nine "produces valid output for mode %s" cases ran BURKES.
Fixed the call to `{ mode }` and added an assertion tying each mode's
case to its own actual dithering behavior (diffing against DitherMode.NONE)
plus a cross-mode difference check, so this kind of hollowing fails loudly.

M3: wasm dither_image's `scheme_id: u8` meant any parsable value with a
non-empty `palette_bytes` silently enabled canonical pinning, so a direct
wasm consumer leaving scheme_id at its default got Mono-canonical pinning
unasked. Changed the parameter to `Option<u8>` (BREAKING WASM SIGNATURE:
scheme_id is now optional/nullable instead of a mandatory u8; an
unparsable Some(id) is now a hard error instead of a silent fallthrough
to the uncanonicalized path) and dropped the 255 sentinel in core.ts for
a real `undefined`.

L1: core.ts trusted the caller's width/height while wasm derives height
from buffer length, so a short buffer silently produced
indices.length !== width * height. core.ts now validates
data.length === width * height * 4 and throws, matching Python's
existing cross-check.

Not a breaking change for the TypeScript public API.
@g4bri3lDev
g4bri3lDev merged commit cfd8e3a into main Jul 21, 2026
7 checks passed
@g4bri3lDev
g4bri3lDev deleted the fix/js-surface-consistency branch July 21, 2026 21:34
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant