"Safety audit — unsafe blocks, memory leaks, undefined behavior, Vulkan spec violations"
Install
npx skillscat add matiaszanolli/byroredux/claude-commands-audit-safety Install via the SkillsCat registry.
Safety Audit
Read _audit-common.md (layout, methodology, dedup, report format) and _audit-severity.md (the unified
scale + Special-Rules table this domain leans on) before starting. Do not restate their content here.
Severity anchors: FFI lifetime violation = CRITICAL · BLAS/TLAS wrong geometry/address or SSBO index
mismatch = CRITICAL · leak that compounds per frame = HIGH · Vulkan spec violation = HIGH ·unsafe without a safety comment = MEDIUM.
Scope: unsafe / FFI / memory-safety / GPU-lifetime rules. Untrusted-input parser discipline (BSA/BA2 size
ceilings, BGSM, CDB, HKX, FaceGen, MenuXml, archive-name slicing) is /audit-parsers; the .pex / .psc
front ends are /audit-papyrus. Dimensions run by blast radius; each has Paths: and First step: — skip
a dimension whose Paths have no commits since the last AUDIT_SAFETY_* report.
Scale of the surface (re-measure; do not trust these figures)
Census recipe: grep -rwo unsafe <crate>/src | wc -l for tokens, grep -rnE 'unsafe[[:space:]]*\{' <crate>/src
for blocks — read the hits: word counts also match prose (byroredux reports 5 tokens and
only 2 real blocks). Re-measured 2026-10-12 at 0d698d16c (the 239 commits since 2026-10-10 added and removed no unsafe; #5273 removed five earlier):
crates/renderer/src: 935 word tokens (944 by the substring recipe; 939 on 2026-10-08) — 715unsafe {
blocks (720), 96 declaredunsafe fn(the loosegrep 'unsafe fn'reads ~145: prose and source-scan
strings), 35unsafe impl(newest:NoUninit for ModelPush), ~811SAFETYmentions. Net shrinks: the
#4599 poison-policy extraction cutvulkan/buffer.rs40 → 19 blocks; #5273 (74ed66f0a) replaced the fiveCStr::from_ptrdevice/extension-name blocks invulkan/device.rswith ash's bounded*_as_c_str()accessors
(pinned bydevice_names_read_through_bounded_accessors);vulkan/groundcover.rssplit intovulkan/groundcover/{construct,frame}.rs(18 + 12). Densest files:vulkan/gpu_timers.rs(62),vulkan/groundcover_bench.rsandvulkan/acceleration/blas_static.rs(29 each). Compare against the previous
report, not this text.- Tail:
crates/fsr3-sys10 blocks (one is the test-onlybyro_fsr3_abi_layoutprobe; Dim 1),crates/nif1
POD-read site + 4unsafe impl AnyBitPattern(Dim 2),crates/core4 inecs/query.rs(/audit-ecsDim 3)- 1 in
string/mod.rs(from_utf8_uncheckedafter an ASCII-only fold) + 1 test-only,crates/pex1
transmute (Dim 2),byroredux2 —cell_loader/unload.rs1 and the NVML GPU-name probe'sget_physical_device_propertiesinapp_events.rs(both commented since b7bc84722/#5120, which also replaced theCStr::from_ptrblock withdevice_name_as_c_str),tools/byro-launcher/src/preflight.rs1 block + 1unsafe fn(Vulkan loader probe),crates/plugintest-only env-var edits,crates/cxx-bridgeoneunsafe extern "C++".
- 1 in
- No
unsafeat all (the remaining hits are prose):crates/{audio,bgsm,boot-request,bsa,debug-protocol, debug-server,debug-ui,facegen,game-detect,hkx,menuxml,mod-runtime,papyrus,physics,platform,save,scripting, sdk,settings-io,sfmaterial,spt,ui}andtools/{byro-dbg,byro-detect,texture-upscale}.
Onlycrates/sdkcarries#![forbid(unsafe_code)]— for every other unsafe-free crate the absence is
unenforced, so re-run the census on each and report any newcomer.
Dimensions
1. FFI Lifetime Safety (live crossings first) — CRITICAL class
Paths: crates/fsr3-sys/, crates/renderer/src/vulkan/frame_upscaler.rs, crates/ui/src/, tools/byro-launcher/src/preflight.rs, crates/cxx-bridge/
First step: grep -nE 'unsafe|# Safety|extern "C' crates/fsr3-sys/src/lib.rs
crates/fsr3-sysis the workspace's only real FFI crossing and sits on the default render path:extern "C"functions over*mut RawContext/*const RawCreateDesc(native shim undercrates/fsr3-sys/native/, vendored FidelityFX C++ — trusted, not audited by Rust rules);Context::createandContext::dispatcharepub unsafe fnwith# Safetydocs (device / physical
device / proc-addr outlive theContext; dispatch handles belong to the creating device);Drop
calls the native destroy, whose Vulkan-idle requirement is part ofcreate's contract. Every newpub unsafe fnneeds a# Safetysection; the renderer call site (frame_upscaler.rs) must honour
it, anddestroy_allocationsordering is/audit-concurrencyDim 6.- Ruffle / wgpu (
crates/ui) is safe Rust end to end. Memory-safety questions: the captured pixel
slice vs. the engine upload —update_rgba/write_rgba_inplacenow copy into an owned per-handle buffer
at queue time (texture_registry/dynamic_rgba.rs), so the borrow ends at the call; what remains is that
module's per-FIF staging arena and consume-only-on-submit (recorded_slot) — and wgpu device/allocator
teardown vs.VulkanContext. Stride/format/resize
contract is/audit-ui. tools/byro-launcher/src/preflight.rsloads the Vulkan loader and creates an instance
(ash::Entry::load,enumerate_physical_devices,destroy_instance): check the instance is destroyed on
every path and no handle escapesprobe.- cxx bridge scope guard:
crates/cxx-bridge/src/lib.rsexposes one fn,native_hello() -> String. No
raw pointers, borrowed slices or shared ownership cross it today, so there is no lifetime surface yet; the scope guard is that no*const,&[u8],Box<…>or Rust-reference-takingunsafe extern "C++"
fn appears (then it becomes a live CRITICAL surface).
2. Memory Corruption / UB
Paths: crates/core/src/ecs/query.rs, crates/nif/src/{stream,header}.rs, crates/pex/src/opcode.rs, crates/renderer/src/vulkan/{buffer,scene_buffer/gpu_types,material}.rs, Cargo.toml
First step: grep -rnE 'transmute|from_raw_parts|set_len|from_utf8_unchecked|unsafe impl' crates byroredux tools --include='*.rs' | grep -v test
- ECS cached-pointer contract (#35/#1367): audit lives in
/audit-ecsDim 3 (four derefs, Miri job); report a
broken guard there, cross-reference here. NoUninitbyte views (vulkan/buffer.rs):unsafe trait NoUninit(local twin ofbytemuck::NoUninit)
gateswrite_mapped,create_device_local_bufferandbyte_view;GpuMaterial::as_bytesand the
dedup hash route throughbyte_view(#4445, #3990). Everyunsafe impl NoUninit for Xmust be#[repr(C)]
with no padding — the layout tests in Dim 6 and inscene_buffer/gpu_instance_layout_tests.rsare its
proof; a struct that gains a hole fails at hash/equality, not silently. A new byte-slice cast that
bypassesbyte_viewis the regression.#[repr(C)]GPU structs (scene_buffer/gpu_types.rs,material.rs): vec3 is three scalarf32, never[f32; 3](std430); drift is per-instance corruption (_audit-severityrepr(C) HIGH row) — pins in Dim 6.- NIF bulk POD reads (
NifStream::read_pod_vec,header::read_pod_vec_from_cursor, single unsafe siteread_pod_vec_frominstream.rs):T: AnyBitPatternis a sealedunsafe trait(impls: primitives, arrays,NiPoint3-style structs,BoneWeight/Meshlet/CullDatainblocks/bs_geometry.rs). Since #4594/#4796 the
site takes the concreteCursor<&[u8]>, never a genericRead(whose impls may read the buffer): it
re-checkscount × size == byte_count, slices the source with checked bounds,copy_nonoverlappings into
fresh capacity, thenset_len; a big-endian compile-error gate protects the LE layout. A regression is any
path that hands spareVeccapacity to aReadimpl again. A newunsafe impl AnyBitPatternneeds a padding-free,
every-bit-pattern-valid proof. - pex opcode decode (
OpCode::from_u8): a realtransmute::<u8, OpCode>, sound only whileOpCodeis#[repr(u8)]with contiguous discriminants0..MAX_OPCODEANDbyte >= MAX_OPCODEis rejected first. The
last-discriminant half is a compile-timeconst _: () = assert!(…)(#4475); contiguity and the runtime bound
are not — a gap in the table or a dropped bound is UB. - LZ4
safe-decodepin (Cargo.toml,byroredux-bsathe sole dependent, #3392): with the feature on,lz4_flex::decompressis bounds-checked; off, a short hint is a heap overflow nocatch_unwindcan
catch. The workspace entry isdefault-features = falsewith an explicit feature list that namessafe-decode— that shape is the pin, not a finding. Verify it survives dependency bumps (it held through 0.14,
20ad630a6);safe-decode
dropped from the list, or a dependent declaringlz4_flexoutsideworkspace = true, is HIGH.
The archive size ceilings themselves are/audit-parsers. - Stack-overflow risk: no unbounded recursion in block-walk / scene-graph traversal (ESM GRUP walkers bounded
per/audit-esm; NIF shape resolution #1385; ECS hierarchy viaHierarchyTraversalGuard).
3. Memory & Resource Leaks, Drop Ordering (HIGH when per-frame/per-cell)
Paths: crates/physics/src/world/, byroredux/src/cell_loader/unload.rs, crates/renderer/src/{deferred_destroy,mesh}.rs, crates/renderer/src/texture_registry/, byroredux/src/app_events.rs
First step: cargo test -p byroredux rapier_release && grep -rn 'DeferredDestroyQueue<' crates/renderer/src | grep -v deferred_destroy.rs
- Rapier bodies on cell unload (#1520):
release_victim_rapier_bodiesfrees bodies, colliders, joints and
broad-phase state per unloaded cell; guardbyroredux/src/cell_loader/rapier_release_tests.rs
(release_sweeps_both_ragdoll_and_rapier_handles) — confirm it still asserts emptiness. - Deferred destroy:
DeferredDestroyQueue<T>(deferred_destroy.rs) has four production instantiations —
mesh vertex/index buffers (mesh.rs), retired instance SSBOs (scene_buffer/buffers.rs), BLAS entries and
BLAS scratch (acceleration/mod.rs). Textures use a different mechanism: per-entrypending_destroydrained
byTextureRegistry::tick_deferred_destroy(texture_registry/release.rs, backlog published astexture_pending_destroy_count). All ticks run AFTER the in-flight fence wait
(context/sync_and_acquire_frame.rs) and shutdown drains; a missed drain leaks GPU memory, a too-early
destroy is UAF (CRITICAL). Skin-compute resources use no shared queue — verify their own path when touched. AllocatorResourcedrop order (#1406): removed from the ECSWorldBEFOREVulkanContext::drop()
(remove_resource/ re-insert inbyroredux/src/app_events.rs); itsDropcalls the driver, so aWorldthat outlives the context makes it call a destroyed device (CRITICAL). Check the
panic-unwind path can't skip the removal.- Allocator-lock poison policy (#4089 → #4599, e94075d75): every free/destroy/
Dropsite routes throughallocator::{lock_recovering, into_inner_recovering, free_allocation_recovering}(a poisoned lock means another
thread panicked; a second panic in teardown aborts or skipsdestroy_device). Direct.expect/.lock().unwrap()
acquires survive only on allocation-side and read-only report paths, pinned per file byallocator_lock_direct_acquires_are_confined_to_allocation_and_reports; #5209 (c22fc3f2a) extended it past the
allocator spelling —VulkanContext::droprecovers the transfer-fence lock
(teardown_path_recovers_poisoned_locks_beyond_the_allocator_family). Flag a new.expectreachable fromDrop/teardown, an allowlist bumped without a reason, or a silent recovery with no rationale comment (#2398); a
create-path panic is a deliberate choice, not automatically a finding. - egui: texture free is deferred one frame (
pending_free) ondraw_frame's fence wait — freeing on the
arriving frame is UAF. Partial deltas are promoted to full uploads from a CPU mirror (#4986), so replacing an
existing id retires an image too; both retirements rest on the all-slots fence wait (#4988, a rider inframes_in_flight_contract_names_every_dependent_resource). AFullOutputthat is never drawn (shutdown, no overlay
pass,VulkanContext::drop) is discarded throughdrop_without_applying_deltas— egui 0.36 debug-asserts on a dropped
undrainedTexturesDelta(500fcb8ca).EguiPassteardown ordering is/audit-concurrencyDim 6. - GPU allocation inventory (BLAS/TLAS + scratch, G-buffer, SVGF/TAA history, caustic accumulators, skin
slots, MaterialBuffer, volumetric/bloom pyramids, ground-cover and sky-bake buffers): each tracked and freed;
budgets aredocs/engine/memory-budget.md— do not re-derive. - CPU unbounded growth:
Vec/HashMapkeyed by cell or path that never shrinks.MaterialTableis
cleared at the top of every frame (material_table.clear(),byroredux/src/render/mod.rs) and cannot grow;AnimationClipRegistrystub growth is documented in/audit-ecsDim 7.
4. Unsafe-Block Discipline — are the stated invariants TRUE?
Paths: crates/renderer/src/vulkan/, crates/renderer/src/lib.rs
First step: grep -rn undocumented_unsafe_blocks crates (expect the one deny in crates/renderer/src/lib.rs, no allow escapes), then the census recipe
- Guard (renderer):
crates/renderer/src/lib.rscarries#, so a
comment-lessunsafe {}in the renderer failscargo clippyin the CI jobTest + Check + Clippy; it is inert undercargo build/cargo test. Since #5121 (530c9e7aa) the workspace step runs--keep-goingand a dedicated step
(cargo clippy -p byroredux-renderer --no-deps -- -D clippy::undocumented_unsafe_blocks) re-lints the renderer on
its own closure, so an upstream crate's new-toolchain clippy failure can no longer disarm the gate — confirm both survive. Confirm thedenyis
present and unescaped. The lint seesunsafe {}blocks only:unsafe fnbodies and the 35 rendererunsafe impls
need a justification found by reading. Crates outside the renderer have no such lint — sweep comment-less blocks
there by hand (at 2026-10-10 all commented infsr3-sys,nif,core,pex,byro-launcherandbyroredux); a
comment-less block is MEDIUM. - The audit's value is therefore the truth of each invariant, not its presence: for each new or changed block, does the
stated precondition (device live, handles from this device, not in flight, pointer valid for the call) hold at THIS call
site? A commented block whose invariant is FALSE is the higher-severity finding. - Do not hunt a "SAFETY vs
unsafecount gap" (#2692): raw token counts includeunsafe fn,unsafe impland prose. - Heaviest in ash FFI wrappers, the gpu-allocator
Arc<Mutex<…>>interplay, andfrom_raw_parts/caston mapped
memory. Prioritise files new or heavily edited since the last report over the nif/core/pex tail.
5. Vulkan Spec Compliance (HIGH — flag what cargo test can't see)
Paths: crates/renderer/src/vulkan/
First step: CI job vulkan-validation (lavapipe, .github/workflows/ci.yml) fails on any [Vulkan] line; locally BYRO_VALIDATION=1. Since #4987 (6d5d8fa5f) the lane globs the lavapipe manifest, goes red on "Vulkan init failed", and requires the Selected GPU: line (vulkan_validation_job_resolves_lavapipe_and_fails_on_init_failure, vulkan_validation_job_requires_a_selected_device), and since #5261 (59115f54c) requires an rt-integrity: … rt_flag=1 … tlas_build=1 line, so a silent RT-off is red (vulkan_validation_job_requires_live_rt, #5416). Only byroredux_renderer::vulkan::device rides at info (#5263, a88100975), so the messenger stays at the error floor and every [Vulkan] line it prints is an ERROR. It boots only the 5-frame default scene — never cite a green run as coverage of a game-data path
Render-pass / barrier / pipeline-state claims invisible to cargo test are "needs validation-layer or RenderDoc
verification" (/audit-concurrency guardrail); report emitted validation errors verbatim.
- Every
vkCreate*has a matchingvkDestroy*; children destroyed before parents, device last. - Acceleration structures: correct geometry flags, valid device addresses,
SHADER_DEVICE_ADDRESSon buffers;
TLAS UPDATE count == BUILD count; skin-BLAS refit vertex/geometry count == BUILD (a bone-count change forces a
rebuild). Wrong AS geometry/address = CRITICAL. The TLAS resizedevice_wait_idle(#1390) is defence behind the
both-slots fence wait (/audit-concurrencyDim 1) — verify one of the two survives. - Depth-capture format (#3570):
depth_capture_record_copy/depth_capture_finish_readback(context/depth_capture.rs)
consult the live depth format; D16 devices refuse rather than misdecode as f32. VK_KHR_ray_queryis enabled and feature-gated before any ray-query use.- Compute layout hygiene: images used as storage-write + sampled-read stay in
GENERAL;initialize_layouts
transitions every mip / FIF slot once; caustic / volumetric-inject CLEAR-before-COMPUTE (a missing clear is
persistent ghost accumulation);vol.dispatch()gated onVOLUMETRIC_OUTPUT_CONSUMED(read the const, don't
assume). Volumetric far-plane lockstep (#3611):VolumetricsConfig::DEFAULT,DEFAULT_GRID_FAR_METERS
(derived) and the literalVOLUME_FAR(shader_constants_data.rs) agree —volume_far_shader_constant_agrees_with_the_volumetrics_config_defaultpins it. - SPIR-V reflection (
vulkan/reflect.rs,scene_buffer/scene_descriptor_reflection_tests.rs) is the one
binding-drift check visible tocargo test; prefer it to eyeballing descriptor writes.
6. GPU Struct Layout Soundness (GpuMaterial and siblings)
Paths: crates/renderer/src/vulkan/{material.rs,material_tests.rs}, crates/renderer/src/vulkan/scene_buffer/, crates/renderer/shaders/include/bindings.glsl
First step: cargo test -p byroredux-renderer gpu_material gpu_instance gpu_camera
- Sizes are pinned by tests named for the size (
gpu_material_size_is_432_bytes—GpuMaterialis 432 B;gpu_instance_is_160_bytes_std430_compatible,gpu_camera_is_368_bytes,gpu_light_is_64_bytes,gpu_terrain_tile_is_176_bytesinscene_buffer/gpu_instance_layout_tests.rs). A test-name-vs-asserted-size
mismatch or a stale number in prose means the GPU reads wrong bytes. Guards that police the prose:bindings_glsl_states_the_real_struct_size,the_pin_test_bindings_glsl_names_actually_existsandgpu_material_size_claims(scanscrates,byroredux,tools,docs/engine,.claude/commands, top-level
docs for stale sizes) inmaterial_tests.rs— run them after editing any audit skill that states a size. - Per-field offsets:
gpu_material_field_offsets_match_shader_contract(#806) — the size pin cannot see a
within-vec4 reorder. Adding a field without updating it is a regression; the other half isgpu_material_glsl_field_names_pinned. - All fields are flat scalar
f32/u32, no[f32; 3], and (since #3909) no explicit padding:NoUninit(Dim 2)
enforces that at the dedup hash. New scalars must be zero inGpuMaterial::default()so default materials dedup
to slot 0. - Intern cap (#797):
MaterialTable::interncaps atMAX_MATERIALS(scene_buffer/constants.rs) and
returns id0with a one-shot warn;upload_materialsdebug_asserts and clamps with.min(MAX_MATERIALS)—
keep both in lockstep. GpuInstance.material_idindexes the SSBO with NO GPU bounds check — the CPU must guarantee in-range (CRITICAL).ui.vertMaterialBuffer read offsets stay in lockstep withbindings.glsl'sGpuMaterial/GpuInstance
(triangle.frag#includes it; #785 was a stale-hunk regression). Standing lockstep rule:GpuInstanceinbindings.glslplus its standalone shader copies change together.
7. GPU-Fed Data & Shader Loop Bounds
Paths: crates/renderer/shaders/triangle.frag, crates/renderer/src/{shader_constants.rs,shader_constants_data.rs}, byroredux/src/material_translate.rs, byroredux/src/app_frame.rs, crates/core/src/ecs/resources/skin_slot_pool.rs
First step: cargo test -p byroredux-renderer shader_constants && cargo test -p byroredux bone_palette_overflow
- RT glass loop bound: the unbounded-recursion guard is
MAX_REFRACT_PASSTHRUS(= 2 + 2 * MAX_RAY_QUALITY_TIER,
emitted into the generatedshader_constants.glsl);triangle.frag's passthrough loop is bounded byint(MAX_REFRACT_PASSTHRUS)and exits early at the adaptiverefractPassthruBudget(2/4/6/8 by quality tier).
Pinned bytriangle_frag_scales_glass_interface_depth_with_honest_ray_cost(shader_constants.rs). Frisvad's
orthonormal basis (include/math_common.glsl) replaces the degeneratecross(N, up)near vertical (#820).GLASS_RAY_BUDGETis now the tier-3 ceiling for telemetry, not a runaway cap (glass_ray_limitstays in
the GPU ABI for comparison only), so an unenforced budget at a call site is not a hang risk. - NaN/Inf reaching the GPU:
translate_materialseedsf32::NANintoMaterial.metalness/roughness;Material::resolve_pbr(/audit-ecsDim 8) is the only place that clamps them. Every renderer-boundMaterial
producer must runresolve_pbr()or build already-finite values. Collision translate
(crates/nif/src/import/collision/mod.rs) half-extents/radii and emitter rate/lifespan/size
(extract_emitter_params→apply_emitter_params) must be finite and bounded at the extract boundary.
Volumetric raw V-buffer history: non-finite guard + finite-fp16 clamp on the store since #4782 (17e17de5e). - Bone palette overflow:
SkinSlotPoolwarns once (overflow_warned, count inoverflow_attempt_count) and
excess entities fall back to bind pose rather than over-indexing; tests inbyroredux/src/render/bone_palette_overflow_tests.rs. - First-sight
bind_inversesrequeue (#3569/#3991):app_frame.rsrolls back pose commits and requeues
pending uploads when!ctx.skin_state_submitted || ctx.bind_inverse_upload_failed.skin_state_submittedis the
SUBMIT-time latch (set only afterqueue_submitreturnsOk); reading the record-timeskin_dispatch_ran
instead loses palettes on the three tailErrsites ofdraw_frame. Verify the check and latch resets survive. - Starfield content is walkable (Cydonia): do not dismiss spawn/animation safety with "no SF content exercises this".
8. Sandboxed Mod Runtime — Trust Boundary (crates/mod-runtime)
Paths: crates/mod-runtime/src/, crates/sdk/src/{identity,service}.rs, byroredux/src/extensions/, workspace Cargo.toml (wasmtime)
First step: cargo tree -p byroredux-mod-runtime | grep -i wasi (must print nothing) and grep -c 'require_' crates/mod-runtime/src/runtime/capabilities.rs
crates/mod-runtime (wasmtime component-model host, no unsafe) is wired into the engine: byroredux/src/extensions/
(~10.8k LOC, unsafe-free) is reached from main.rs (load_requested_extensions, queue_session_event) andapp_events.rs (shutdown_extension_host, extension_ui_menu_sync). Audit it as a live path.
- Absence, not promise: the crate doc says no WASI is linked;
wasmtime(49.0.2 since 15a6b1d2d) is declared withdefault-features = false, features = [anyhow, component-model, cranelift, runtime, std]. Awasifeature pulled
in transitively turns the claim false (thewasi/wasip2entries inCargo.lockare mio / getrandom
wasm-target deps — the First-stepcargo tree -pis the check, not a lockfile grep). - Capability gating: authority types live in
crates/sdk/src/identity.rs(CapabilitySet,CapabilityId;Principalstays incrates/mod-runtime/src/identity.rs); the 28*_CAPABILITYconstants are incrates/sdk/src/service.rs. Gating is centralised incrates/mod-runtime/src/runtime/capabilities.rs(require_*
guards) and used fromruntime/host/*.rs; registration-time checks (grants.contains(...)for script functions,
console, settings, events) live inbyroredux/src/extensions/{install,dispatch}.rs. No test enumerates host
functions against capabilities, so enumerate theimpl <wit>::Host for HostStatefns and confirm each reaches arequire_*before acting (or is documented capability-free), and that a missing grant is anErr, never a no-op
(a swallowed denial looks like success to the guest and to the log reader). - Every
*_CAPABILITYconstant has a real call site:/audit-tooling(the reverse direction of the check above). - Per-instance isolation: each
ModInstancehas its own principal and store; nostatic, sharedArcor global
buffer lets one instance observe or affect another (matters with several mods hosted side by side). - Resource limits:
SandboxConfig::validate()(limits.rs) rejects absurd limits (MAX_SANE_LIMIT,MAX_WASM_STACK_BYTES_CEILING,MAX_FUEL_PER_ENTRY); fuel exhaustion yields aFaultInfo/ terminalInstanceStatus, not a hang; guest-controlled buffers are capped (max_log_entries/max_log_message_bytes/max_log_bytes, command and console-output budgets) with distinctFaultKinds.SandboxRuntime::newpins the
wide-arithmetic and relaxed-SIMD proposals off (wasmtime 49 flipped the first on; the second lowers per CPU), and each
entry setsStore::set_hostcall_fuel(max_log_message_bytes + 1024)so a WITStringarg cannot make the host lift the
default 128 MiB before the size checks run (0781077b8,runtime/sandbox.rs). Fuel also chargesmemory.fill/copy/initper byte since wasmtime 48, pinned bymemory_fill_is_charged_per_byte_and_quarantines(9046b5ac5). - Lifecycle: a fault in one
LifecyclePhasecannot leave the instance usable;shutdownis idempotent; a
trapping guest is quarantined, not retried in a loop. - Untrusted bytes at compile time:
SandboxRuntime::compiletakes arbitrary bytes bounded bymax_component_bytes; a malformed component isSandboxError, never a panic. - Guest entry vs ECS guards / host
Mutex:/audit-concurrencyDim 3.
Procedure
- Census (
Scale of the surface); note new unsafe files since the last report. Thecargo test -p byroredux …
first steps (Dims 3, 7) need rustc ≥ 1.96 (wasmtime 49's MSRV) through the rustup cargo —docs/contributing.md
§ Toolchain note. - Dims 1–2 first (live FFI, UB); then leaks/drop order (3), unsafe sweep (4), Vulkan spec (5), GPU layout (6), GPU-fed
data (7), mod-runtime (8). - Dedup against open/closed issues (
_audit-commonDeduplication) — most items are regression guards; report an
intact guard as PASS, not a NEW finding. - Save to
docs/audits/AUDIT_SAFETY_<TODAY>.md(see_audit-commonReport Finalization).