Prompt and scope
A SaaS platform must load third-party rule plugins. Plugins may be written in different languages, and the host wants to reuse them in one process or a lightweight runtime while granting only explicit clock, logging, and object-storage interfaces. Explain the relationship between core WebAssembly modules, the Component Model, WIT, and WASI, then propose versioning, performance, security, and operations controls.
This tests cross-language runtimes and security boundaries, not the phrase “near-native speed.” The WebAssembly 3.0 specification defines core Wasm as a validated, sandboxed virtual instruction set. The Component Model adds typed interfaces, composition, and calling conventions; WIT describes interfaces; and WASI provides system capabilities in WIT form. A good answer separates “the module executes” from “the plugin can safely use host capabilities.”
What the interviewer is testing
A strong answer starts with the smallest capability set, defines plugin interfaces, and then decides whether a Component is justified instead of giving every plugin a host process. It distinguishes shared linear memory in core modules from typed Component boundaries, explains how a world declares imports and exports, and shows how the host rejects undeclared imports.
It also covers reality: a sandbox is not business security if the host exposes a data-exporting API; crossing the boundary can copy large values; and Component Model and WASI versions need a locked runtime matrix. Interview preparation material commonly treats portability, isolation, interface design, and recovery as engineering signals rather than trivia about one tool.
Questions to clarify first
Plugin trust and isolation target
Ask whether plugins are first-party, partner-owned, or fully untrusted. If an attacker may escape the host process, one Wasm sandbox is insufficient; use process isolation or a dedicated service. Confirm whether network, files, randomness, and clocks are required because each expands the capability boundary.
Call model and data size
Clarify whether calls are short synchronous rules, long jobs, or streams. Small structured values fit an interface boundary; large files should use controlled handles or host-managed objects to avoid repeated copying. Latency and concurrency targets determine pooling, warm-up, and backpressure.
Version and failure contract
Ask who upgrades first, whether old interfaces must coexist, whether a failed call may retry, and whether side effects are idempotent. If a plugin writes to an external system, define unknown outcomes after timeouts, idempotency keys, and audit records.
30-second answer framework
“I would treat a plugin as a Component that interacts only through declared interfaces. I would define a minimal WIT world, such as exporting evaluate while importing logging and restricted object reads; the host instantiates it from an allow-list and supplies nothing else. WASI is a standardized family of system interfaces, not automatic permission to files or networks. Core Wasm provides validated execution, while the Component Model provides typed cross-language composition. I would version interfaces, bound resources, and test privilege, compatibility, performance, and recovery. If the plugin is fully untrusted or needs extensive shared state, I would choose a separate service.”
Step-by-step solution
Step 1: Start with capabilities and trust
Write each plugin’s needs as a capability list: logging, current time, configuration reads, object reads, or queue submission. A capability is a real import, not a comment. A pure-computation world should not have file or network interfaces. For untrusted plugins, treat Wasm as one layer and add process limits, signed releases, and dependency review.
Step 2: Distinguish a core module from a Component
A core module defines low-level functions, tables, linear memory, and imports and exports, which works well inside one runtime or language binding. Passing strings and lists between modules often requires a shared-memory layout and hand-written ABI, creating language coupling. A Component packages one or more core modules in a self-describing container, uses interface types for strings, records, variants, and results, and applies a Canonical ABI for boundary adaptation.
world rules {
import logger: interface { log: func(level: string, message: string) }
import objects: interface { read: func(key: string) -> result<list<u8>, not-found> }
export evaluate: func(input: list<u8>) -> result<list<u8>, rule-error>
}This WIT-like declaration expresses a boundary, not an implementation language. The host validates that the component declaration matches the permitted world and rejects extra imports; the plugin sees interfaces rather than host pointers or file descriptors.
Step 3: Explain WIT worlds and WASI
WIT defines interfaces and import/export direction; a world is a closed contract containing those interfaces. A host can connect one component’s export to another component’s import without shared memory. WASI uses the same interface-description style for files, networking, clocks, and other system capabilities, but the runtime still configures directory mappings, network policy, and resource limits. Implementing WASI does not open every capability.
Step 4: Design lifecycle and resource controls
Set per-instance memory, call-time, fuel or execution, concurrency, and output-byte limits. Short calls can use a pool and clear state between requests; stateful plugins should keep state in explicit host objects. On cancellation, stop new calls, wait for or terminate the instance, and record whether an external side effect may have happened. A returned error does not prove that an external write rolled back.
Step 5: Handle versions and language interoperability
Adding optional interface fields is usually safer than changing existing semantics; removal or meaning changes need a new world or migration window. Lock the WIT package, runtime, adapters, and capability manifest in the release, and record the component hash and dependency versions. Shadow-run an upgrade, compare results, latency, and resource use, then canary by tenant or plugin version. Keep old plugins on the old world rather than coercing new semantics into it.
Step 6: Prove boundaries and value with tests
Security tests cover undeclared imports, path traversal, network escapes, resource exhaustion, maliciously large results, and invalid component signatures. Compatibility tests generate the same WIT values in multiple languages and cover optional fields, error variants, and independent host/component upgrades. Performance tests separate boundary adaptation, cold start, pool hit, and business computation. If copying dominates, a native library or separate service may be the better design.
High-quality sample answer
I would first confirm plugin trust and capabilities. For a controlled partner plugin, I would define a minimal WIT world: the plugin exports rule evaluation and imports structured logging and restricted object reads. The host connects only those imports and enforces memory, time, concurrency, and output limits. A pure-computation world has no file or network capability.
Core Wasm supplies validated low-level execution, but cross-language compound values can depend on a shared-memory ABI. The Component Model wraps modules with typed interfaces and a Canonical ABI; WIT describes direction, while WASI is an optional family of standard system capabilities. I still configure directory, network, and clock permissions. Releases lock the WIT package, adapters, runtime, and component hash. Old worlds remain available while a new version is shadowed and canaried.
Finally I would test privilege, resource exhaustion, retry behavior, and external side effects, measuring cold start, boundary adaptation, pool hits, and business time. If a plugin is fully untrusted, requires complex networking, or shares a large mutable state, I would use a separate service with stronger process isolation instead of treating the Wasm sandbox as a complete security solution.
Common mistakes
- Treating WebAssembly as a process with direct syscalls → core modules have no environment API; capabilities arrive through host imports → List each import and configure least privilege.
- Treating a Component as merely a faster core module → its main value is typed interfaces, composition, and cross-language ABI, which can add adaptation cost → Benchmark cold start, adaptation, and business work separately.
- Granting every plugin full WASI → file, network, and clock capabilities enlarge data-leak and resource-abuse paths → Grant capabilities per world and tenant, defaulting to deny.
- Validating only the return value → an external write may have happened before a timeout, so retrying can duplicate the side effect → Define idempotency, audit, and unknown-result states.
- Forcing a new host interface into an old plugin → equal types do not guarantee equal semantics → Support versioned worlds and explicit migration.
Follow-up questions and responses
Follow-up 1: Why not run plugins in containers?
Containers fit plugins needing an independent filesystem, network stack, or process failure boundary, but startup and resource costs are usually higher. Components fit short calls, explicit capabilities, and dense instances. If a plugin needs kernel privileges, special drivers, or stronger isolation, use a container or service. Choose from threat model, startup target, resource budget, and operations maturity.
Follow-up 2: A component times out after an external write. Can you retry?
A timeout cannot prove that the write did not happen. Pass a request ID and idempotency key to the external system, record UNKNOWN, and confirm through a query or compensation workflow. Retry only after confirming no execution or when the external contract guarantees idempotency; the Component Model does not provide a cross-system transaction.
Follow-up 3: How do you stop a plugin from exfiltrating data through logs?
Logging is also a capability. Limit field size, structure, and rate; classify sensitive fields and isolate tenants. A high-risk plugin can use a redacting proxy or receive no logging import. Runtime audit records version, capabilities, and call counts, not log content as a security proof.
Follow-up 4: What if the WASI Preview version changes?
Put the WASI interface package and runtime version in the component release manifest, and make the compatibility matrix a deployment gate. Run the new runtime against the old world with regression and result comparisons; retain the old runtime or adapter when semantics differ. “Implements WASI” is not a cross-version compatibility promise.