Skip to content

Audit fixes: 32-bit sentinel bug, repo identity, pyramid lifecycle, release gate, FFI hardening - #7

Merged
Alek99 merged 6 commits into
mainfrom
audit-fixes
Jul 10, 2026
Merged

Audit fixes: 32-bit sentinel bug, repo identity, pyramid lifecycle, release gate, FFI hardening#7
Alek99 merged 6 commits into
mainfrom
audit-fixes

Conversation

@Alek99

@Alek99 Alek99 commented Jul 10, 2026

Copy link
Copy Markdown
Member

Fixes the P0/P1 findings from a full architecture audit. Six commits, each self-contained with tests.

Correctness

  • 32-bit usize::MAX sentinel detection (_native.py): the five size-returning kernel wrappers compared the Rust error sentinel against 2**64-1. On 32-bit targets (armv7 / win32 / wasm32 — all shipped wheels) c_size_t is 32 bits, so error returns — including panics converted by the ffi_guard shield — were sliced as valid data from an uninitialized buffer. Now derived from ctypes.c_size_t(-1).value; the regression test simulates the 32-bit ABI and fails on the old comparison. The -O-strippable assert in zone_maps is now a real check.
  • FFI size arithmetic (src/lib.rs): checked_mul on grid size products in fc_bin_2d, fc_bin_2d_indices, fc_m4_indices, fc_pyramid_compose (matching what fc_rasterize already did), and a len > u32::MAX ceiling in the index-emitting entry points where u32 row ids would wrap into valid-looking wrong rows. No ABI signature changes.

Lifecycle

  • Native pyramid handles no longer leak: _ensure_pyramid registers a weakref.finalize on the Trace (per the rust-engine design doc §3.3), the append path frees through a helper that disarms the finalizer, and memory_report() gains a pyramid_bytes line (§27: if it isn't in the report it isn't real). Previously a Figure discarded by a notebook cell re-run leaked its pyramid in the process-lifetime registry.

Release safety

  • Tag↔version↔CHANGELOG gate: new scripts/check_release_version.py runs first in the publish job on tag pushes — the tag must equal v<pyproject version> and CHANGELOG.md must carry a dated entry for it. Needled into verify_ci_workflow.py and tested. Note: cutting the next release now requires dating the ## [0.1.0] changelog heading, by design.

Identity / docs

  • Canonical repo identity: SECURITY.md private-vulnerability-report URL, CONTRIBUTING.md clone URL, and the CodSpeed badge pointed at a stale personal repo (Alek99/charts-exp); all now point at reflex-dev/reviz, pyproject.toml gains [project.urls], and check_claim_guardrails.py now rejects stale repo identities (SECURITY.md/CONTRIBUTING.md are scanned too).
  • Demo notebook counts: the "10M-point line" cell allocated 10_000_00000 = 1 billion points (8–16 GB); the "30M scatter" allocated 300M. Aligned code, headings, and titles.
  • Fixed the stale "falls back to NumPy" sdist comment in release.yml and a truncated comment in ci.yml.

Verification

Full pytest suite (904 passed), cargo test (61 passed), cargo clippy -D warnings, ruff check + format --check, ty check, check_public_api, check_python_floor, check_claim_guardrails, verify_ci_workflow, node js/build.mjs --check, and scripts/abi_smoke.py (70/70) all pass locally.

🤖 Generated with Claude Code

Alek99 added 6 commits July 9, 2026 16:46
The size-returning kernel wrappers compared the Rust error sentinel against
2**64-1. On 32-bit targets (armv7 / win32 / wasm32 — all shipped wheels)
c_size_t is 32 bits, so the sentinel arrived as 2**32-1, never matched, and
error returns — including panics converted by the ffi_guard shield — were
sliced as valid data from an uninitialized buffer.

Derive the sentinel from ctypes.c_size_t once at module level, use it at all
five call sites, and replace the -O-strippable zone_maps assert with a real
check. The regression test simulates the 32-bit ABI and fails on the old
comparison.
SECURITY.md routed private vulnerability reports to a stale personal repo;
CONTRIBUTING.md cloned it; the CodSpeed badge linked it; CLAUDE.md carried the
old project name; PyPI metadata had no [project.urls] at all. Point everything
at github.com/reflex-dev/reviz, and teach check_claim_guardrails.py to reject
stale repo identities in public docs (SECURITY.md and CONTRIBUTING.md are now
scanned too). Also drop an internal dev-sandbox reference from
docs/benchmark.md.
_ensure_pyramid stashed a raw registry handle on the trace, and the only
free was on the streaming-append path — a Figure discarded any other way
(the notebook cell-re-run pattern) leaked the pyramid in the process-lifetime
Rust registry. Register a weakref.finalize on the Trace so GC frees the
handle, route the append path through the same helper so an explicit free
disarms the finalizer, and add a pyramid_bytes line to Figure.memory_report()
(§27: if it isn't in the report it isn't real).
The '10M-point line' cell allocated n = 10_000_00000 — one billion points,
8-16 GB of arrays on a laptop — and the '30M-point scatter' cell allocated
300M with a title saying 300M under a 30M heading. Align code, headings, and
chart titles at the advertised 10M/30M.
release.yml published whatever version sat in pyproject.toml under any v* tag;
nothing checked the tag against the package version or required a dated
CHANGELOG entry. Add scripts/check_release_version.py as the first step of the
publish job on tag pushes, needle it in verify_ci_workflow.py, and test it.
Also fix the stale 'falls back to NumPy' sdist comment in release.yml (there
is no fallback) and restore a truncated comment in ci.yml.
fc_rasterize guarded its size product with checked_mul but fc_bin_2d,
fc_bin_2d_indices, fc_m4_indices, and fc_pyramid_compose built slices from
bare w*h / n_buckets*4, which wrap silently in release builds into
shorter-than-expected slices. Guard all four the same way. Index-emitting
entry points (m4, range, bin_2d_indices) now also reject len > u32::MAX,
where emitted u32 row ids would wrap into valid-looking wrong rows.
All guards fire before any pointer is dereferenced; error sentinels match
each entry point's existing convention. No ABI signature changes.
@Alek99
Alek99 merged commit 8df7381 into main Jul 10, 2026
4 of 7 checks passed
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