Security Best Practices
Practical guidance for running Wasm modules securely with Endive.
Writing Safe Host Functions
Host functions are the primary attack surface — they cross the sandbox boundary and execute with full JVM privileges.
Always validate arguments from Wasm:
HostFunction readMemory = (Instance instance, long... args) -> {
int offset = (int) args[0];
int length = (int) args[1];
// Validate bounds BEFORE accessing memory
var memory = instance.memory();
if (offset < 0 || length < 0 || Math.addExact(offset, length) > memory.pages() * 65536) {
throw new TrapException("out of bounds memory access");
}
byte[] data = memory.readBytes(offset, length);
// ... process data safely
return new long[0];
};
Key rules:
- Validate all memory offsets and lengths before reading/writing
- Never use Wasm-provided values as array indices without bounds checking; keep overflow in mind or use overflow-safe methods such as
Math#addExactorObjects#checkFromIndexSize - Be cautious with string decoding — enforce maximum lengths
- Avoid exposing file paths, SQL queries, or shell commands derived from Wasm input
WASI Sandboxing
WASI file access does not enforce path sandboxing by default. Passing the host filesystem directly exposes all files the JVM process can access.
Use a virtual filesystem:
var options = WasiOptions.builder()
// Use ZeroFs to restrict access to specific directories
.withDirectory("/guest/data", zeroFs.getPath("/host/sandboxed/data"))
.build();
Provide only access to what is needed by the Wasm module, especially if it is untrusted:
- Pass a virtual file system, and copy needed data to it in advance
- Pass only the needed environment variables and not all host environment variables
- Inherit the host stdin, stdout and stderr only if needed; alternatively provide a custom
InputStreamandOutputStream
Resource Limits
Wasm modules can consume unbounded CPU and memory. Always set limits when running untrusted code.
CPU timeout via thread interruption:
var executor = Executors.newSingleThreadExecutor();
var future = executor.submit(() -> instance.export("run").apply());
try {
var result = future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true); // interrupts the Wasm execution
}
See CPU Limits for more patterns.
Compiler Security
The runtime and build-time compilers translate Wasm to JVM bytecode for performance. When running untrusted modules:
- Prefer the interpreter for maximum sandbox assurance — it doesn't generate JVM bytecode
- Protect compiler cache directories with restrictive permissions (
chmod 700) if using the directory cache - Set JVM heap limits when compiling large untrusted modules to prevent resource exhaustion
Dependency Supply Chain
- All Endive dependencies are checked against the Bytecode Alliance allowed license list in CI
- Dependency vulnerabilities are scanned nightly via OWASP Dependency-Check
- Dependabot is enabled for automated security updates
- All dependency updates are manually reviewed before merging