Summary
module.toml's [module].component field is documented/intended to carry a content hash the runtime should validate against the loaded .wasm bytes before instantiation. It's parsed, but never read again anywhere in the codebase — a tampered, corrupted, or hand-edited-without-recomputing artifact loads and runs exactly as if it matched.
Affected code
crates/nexum-runtime/src/manifest/types.rs — ModuleSection (component: String)
crates/nexum-runtime/src/supervisor.rs — component load path (Component::from_file)
Finding
ModuleSection carries #[allow(dead_code)] // version + component parsed for future 0.3 hash-verification. — an explicit admission the field is currently inert.
A grep of the entire crate for any read of this field, and for sha256/Sha256 anywhere in the source, turns up zero hits outside the field declaration itself. The actual load path — Component::from_file(engine, &entry.path) — loads the .wasm bytes straight from disk and proceeds directly into enforce_capabilities() (WIT-import gating); there is no comparison against module.component anywhere before or after.
A malformed hash string (wrong length, non-hex, wrong scheme prefix) has no effect at all, since nothing ever inspects the field's contents.
There is currently only one content-loading backend (local filesystem path from engine.toml) — no remote/content-addressed backend exists yet to test unevenly.
Impact
A .wasm artifact that doesn't match its manifest's declared hash — a corrupted download, a tampered artifact, or a hand-edited manifest with a stale hash — loads and runs identically to a valid one. This covers both accidental drift (operator mistake) and a more adversarial scenario (a compromised artifact store or MITM'd download swapping the .wasm bytes).
Suggested fix
Compute sha256 of the loaded component bytes and compare against module.component before instantiation; reject on mismatch. (Already flagged as planned for 0.3 per the existing code comment — this issue is to make sure it's tracked as real, current-state exposure rather than staying implicit in a dead_code annotation.)
Found via
Internal red-team exercise (operator mistake persona).
Summary
module.toml's[module].componentfield is documented/intended to carry a content hash the runtime should validate against the loaded.wasmbytes before instantiation. It's parsed, but never read again anywhere in the codebase — a tampered, corrupted, or hand-edited-without-recomputing artifact loads and runs exactly as if it matched.Affected code
crates/nexum-runtime/src/manifest/types.rs—ModuleSection(component: String)crates/nexum-runtime/src/supervisor.rs— component load path (Component::from_file)Finding
ModuleSectioncarries#[allow(dead_code)] // version + component parsed for future 0.3 hash-verification.— an explicit admission the field is currently inert.A grep of the entire crate for any read of this field, and for
sha256/Sha256anywhere in the source, turns up zero hits outside the field declaration itself. The actual load path —Component::from_file(engine, &entry.path)— loads the.wasmbytes straight from disk and proceeds directly intoenforce_capabilities()(WIT-import gating); there is no comparison againstmodule.componentanywhere before or after.A malformed hash string (wrong length, non-hex, wrong scheme prefix) has no effect at all, since nothing ever inspects the field's contents.
There is currently only one content-loading backend (local filesystem path from
engine.toml) — no remote/content-addressed backend exists yet to test unevenly.Impact
A
.wasmartifact that doesn't match its manifest's declared hash — a corrupted download, a tampered artifact, or a hand-edited manifest with a stale hash — loads and runs identically to a valid one. This covers both accidental drift (operator mistake) and a more adversarial scenario (a compromised artifact store or MITM'd download swapping the.wasmbytes).Suggested fix
Compute
sha256of the loaded component bytes and compare againstmodule.componentbefore instantiation; reject on mismatch. (Already flagged as planned for 0.3 per the existing code comment — this issue is to make sure it's tracked as real, current-state exposure rather than staying implicit in adead_codeannotation.)Found via
Internal red-team exercise (
operator mistakepersona).