Skip to main content

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:

Example
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#addExact or Objects#checkFromIndexSize
  • Be cautious with string decoding — enforce maximum lengths
  • Avoid exposing file paths, SQL queries, or shell commands derived from Wasm input

WASI Sandboxing​

Security Consideration

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:

Example
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 InputStream and OutputStream

Resource Limits​

Wasm modules can consume unbounded CPU and memory. Always set limits when running untrusted code.

CPU timeout via thread interruption:

Example
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