This document describes the architecture of the Munition capability attenuation framework.
Munition provides sandboxed execution of WASM modules with three core guarantees:
- Bounded Execution: All executions terminate (via fuel exhaustion)
- Memory Isolation: No state leaks between executions
- Forensic Capture: Failure state is preserved for analysis
┌────────────────────────────────────────────────────────────────────┐
│ Public API │
│ Munition.fire/4 │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Instance Manager │
│ Munition.Instance.Manager │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
│ │ Compile │→ │ Instantiate │→ │ Execute │ │
│ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Forensic Capture │ │
│ │ (on any failure path) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────┬──────────────────────────────────┘
│
┌─────────────────────────────────▼──────────────────────────────────┐
│ Runtime Behaviour │
│ Munition.Runtime │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ Wasmex (default) │ │ (future runtimes) │ │
│ │ via Wasmtime │ │ │ │
│ └────────────────────┘ └────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
The main entry point. Provides:
fire/4: Execute WASM function with optionsfire_pooled/4: Execute using instance pool (future)validate/2: Validate WASM module
Manager (manager.ex):
- Orchestrates the compile → instantiate → execute → cleanup lifecycle
- Ensures forensic capture on any failure path
- Manages execution timeout (via Task supervision)
State (state.ex):
- Tracks instance lifecycle state
- Used by future pooling implementation
Behaviour (behaviour.ex):
- Defines the runtime contract
- Allows pluggable WASM engines
Wasmex Implementation (wasmex.ex):
- Default implementation using Wasmex/Wasmtime
- Handles fuel configuration
- Translates Wasmtime errors to Munition semantics
Config (config.ex):
- Runtime configuration
- Feature detection
Dump (dump.ex):
- Structured crash dump format
- Serialization/deserialization
- Compressed memory storage
Capture (capture.ex):
- Immediate state capture on failure
- Works even after traps
Analyser (analyser.ex):
- Pattern searching
- String extraction
- Memory introspection
Policy (policy.ex):
- Default fuel allocations
- Complexity-based fuel calculation
Meter (meter.ex):
- Consumption tracking
- Historical statistics
Functions (functions.ex):
- Host function registry
- Capability-gated access
Capabilities (capabilities.ex):
- Capability definitions
- Validation and expansion
1. fire(wasm, function, args, opts)
2. Manager.execute()
a. Runtime.compile(wasm) → module_ref
b. Runtime.instantiate(module_ref) → instance, store
c. Runtime.call(instance, function, args)
- Consumes fuel
- Returns result
d. Build metadata (fuel_remaining, execution_time)
e. Runtime.cleanup(instance)
3. Return {:ok, result, metadata}
1. fire(wasm, function, args, fuel: 100)
2. Manager.execute()
a. Runtime.compile(wasm) → module_ref
b. Runtime.instantiate(module_ref) → instance, store
c. Runtime.call(instance, function, args)
- Fuel exhausted mid-execution
- Returns {:error, :fuel_exhausted}
d. Capture.capture(instance, store, context)
- Captures memory snapshot
- Records fuel state
e. Runtime.cleanup(instance)
3. Return {:crash, :fuel_exhausted, forensics}
1. fire(wasm, function, args)
2. Manager.execute()
a. Runtime.compile(wasm) → module_ref
b. Runtime.instantiate(module_ref) → instance, store
c. Runtime.call(instance, function, args)
- Trap occurs (unreachable, div-by-zero, etc.)
- Returns {:error, :trap, details}
d. Capture.capture(instance, store, context)
- Memory is still accessible after trap
- Captures complete state
e. Runtime.cleanup(instance)
3. Return {:crash, :trap, forensics}
Each fire/4 call creates a completely new WASM instance:
- Fresh linear memory (zero-initialized)
- Fresh global variables
- Fresh fuel allocation
- No shared state with any other execution
This is achieved by:
- Not pooling instances (each call compiles and instantiates)
- Never reusing stores across executions
- No mutable state in the Elixir supervision tree
Host functions are gated by capabilities:
# Only time access granted
Munition.fire(wasm, "func", [], capabilities: [:time])
# WASM can call get_time_ms()
# WASM cannot call get_random() - not grantedThe capability check happens at import resolution time, not at call time. This means:
- Denied capabilities result in instantiation failure
- No runtime overhead for capability checks
- Clear audit trail of what was granted
Pre-compile and pre-instantiate modules for reduced latency:
# Start pool with pre-warmed instances
Munition.Pool.start_link(:my_plugin, wasm_bytes, size: 10)
# Execute using pooled instance (faster startup)
Munition.fire_pooled(:my_plugin, "process", [data])Challenges:
- Memory reset between executions
- Fuel reset
- State clearing
Potential approach:
- Use Wizer for snapshot-and-restore
- Or accept compilation overhead and only cache modules
The Munition.Runtime behaviour allows plugging in different WASM engines:
- Wasmer: Alternative to Wasmtime
- WAMR: Lightweight interpreter for constrained environments
- Native Lunatic: When running under Lunatic, use its WASM support
The framework is designed to support multiple source languages:
- Rust → WASM: Direct, additive isolation
- PHP → WASM: Via php-wasm (restrictive)
- JavaScript → WASM: Via AssemblyScript (restrictive)
- Pony → WASM: Hypothetical, capability-preserving
Each attenuator would be a separate project that produces WASM compatible with Munition's execution model.
┌─────────────────────────────────────────────────────────┐
│ Trusted (Elixir) │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Munition Framework │ │
│ │ ┌───────────────────────────────────────────┐ │ │
│ │ │ WASM Sandbox (Wasmtime) │ │ │
│ │ │ ┌─────────────────────────────────────┐ │ │ │
│ │ │ │ Untrusted WASM Code │ │ │ │
│ │ │ │ (capability-bounded) │ │ │ │
│ │ │ └─────────────────────────────────────┘ │ │ │
│ │ └───────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
- Termination: Fuel exhaustion guarantees termination
- Memory Safety: WASM linear memory is bounds-checked
- Isolation: No shared state between executions
- Capability Restriction: Only granted capabilities accessible
- Forensic Preservation: Failures are captured for audit
- Timing: Execution time is bounded but not constant
- Side Channels: CPU cache timing attacks are possible
- Resource Exhaustion: Memory allocation before instantiation
- Host Function Safety: Custom host functions must be secure