Static read-through audit of crates/dpp-plugin-host, the Wasm plugin sandbox. Guest pointer/length pairs are properly bounds-checked and size-capped; no unsafe in this crate. Four verified findings.
1. Table growth isn't metered, unlike linear memory
Location: src/runtime.rs:63-70
Issue: ResourceLimiter::table_growing always returns Ok(true), in contrast to memory_growing, which enforces memory_cap_bytes. The doc comment claims linear memory up to DEFAULT_MEMORY_CAP_BYTES is "the enforced bound."
Failure scenario: A plugin can issue a single table.grow with a huge element count to force a large host-side allocation, metered by fuel only as one instruction — not by the amount grown. A real resource-limit bypass distinct from the (properly capped) linear-memory path.
2. Sandbox-event metrics are spoofable by plugin-controlled text
Location: src/host.rs:142-168
Issue: Fuel-exhaustion and memory-cap detection classify the error via msg.to_lowercase().contains("fuel") / contains("memory cap") on e.to_string(), which partly derives from plugin-controlled PluginError text (Internal(String), etc., all fields are guest-controlled).
Failure scenario: A plugin returning PluginError::Internal("fuel".into()) triggers the plugin_fuel_exhausted_total metric/alert without ever exhausting fuel — directly contradicting the code's own comment that this alert is "unambiguous."
3. Provisional/in-force compliance gate is bypassable on one output path
Location: src/host.rs:53-97 (generate_passport_payload) vs 184-185 (compute)
Issue: PluginHost::compute centrally enforces gate_determination(catalog().is_in_force(key), ...) so a provisional sector can never surface a binding compliance status. generate_passport_payload has no equivalent — it only injects an advisory __isInForce flag into the input and trusts the plugin to self-limit.
Failure scenario: A plugin can ignore that flag and emit a fully "compliant"-looking passport for a sector that isn't legally in force, with no host-side backstop on this path.
4. Unsigned-plugin loading fails open with no environment guard
Location: src/loader/plugin.rs:42-64, src/loader/signing.rs
Issue: When trusted_key is None, loading proceeds with only a tracing::warn!. The doc comment says "Production deployments must always provide a key," but nothing enforces that.
Failure scenario: A misconfigured deploy silently runs unsigned/unverified plugins.
Static read-through audit of
crates/dpp-plugin-host, the Wasm plugin sandbox. Guest pointer/length pairs are properly bounds-checked and size-capped; nounsafein this crate. Four verified findings.1. Table growth isn't metered, unlike linear memory
Location:
src/runtime.rs:63-70Issue:
ResourceLimiter::table_growingalways returnsOk(true), in contrast tomemory_growing, which enforcesmemory_cap_bytes. The doc comment claims linear memory up toDEFAULT_MEMORY_CAP_BYTESis "the enforced bound."Failure scenario: A plugin can issue a single
table.growwith a huge element count to force a large host-side allocation, metered by fuel only as one instruction — not by the amount grown. A real resource-limit bypass distinct from the (properly capped) linear-memory path.2. Sandbox-event metrics are spoofable by plugin-controlled text
Location:
src/host.rs:142-168Issue: Fuel-exhaustion and memory-cap detection classify the error via
msg.to_lowercase().contains("fuel")/contains("memory cap")one.to_string(), which partly derives from plugin-controlledPluginErrortext (Internal(String), etc., all fields are guest-controlled).Failure scenario: A plugin returning
PluginError::Internal("fuel".into())triggers theplugin_fuel_exhausted_totalmetric/alert without ever exhausting fuel — directly contradicting the code's own comment that this alert is "unambiguous."3. Provisional/in-force compliance gate is bypassable on one output path
Location:
src/host.rs:53-97(generate_passport_payload) vs184-185(compute)Issue:
PluginHost::computecentrally enforcesgate_determination(catalog().is_in_force(key), ...)so a provisional sector can never surface a binding compliance status.generate_passport_payloadhas no equivalent — it only injects an advisory__isInForceflag into the input and trusts the plugin to self-limit.Failure scenario: A plugin can ignore that flag and emit a fully "compliant"-looking passport for a sector that isn't legally in force, with no host-side backstop on this path.
4. Unsigned-plugin loading fails open with no environment guard
Location:
src/loader/plugin.rs:42-64,src/loader/signing.rsIssue: When
trusted_keyisNone, loading proceeds with only atracing::warn!. The doc comment says "Production deployments must always provide a key," but nothing enforces that.Failure scenario: A misconfigured deploy silently runs unsigned/unverified plugins.