When handling SaaS or enterprise systems in custom development, this inquiry inevitably arrives at least once: "Calculation logic differs slightly for each client, so we want to let them plug it in via the UI," or "No-code formula inputs aren't enough, so we want to let them write simple scripts." As a requirement, it is completely natural. However, attempting to implement this straightforwardly turns into a rather dangerous proposition: executing user-written code on your own servers. Settle it with eval, and files and networks are seized instantly; isolating with separate processes or containers imposes heavy operational overhead. Consequently, it often gets shelved as "a feature we want, but too scary to proceed with."
WebAssembly (WASM) and WASI are becoming realistic answers to this dilemma unique to custom development: wanting to run untrusted code safely yet lightly. Entering 2026, WASI 0.3 became an official release, incorporating asynchronous Component processing into the shared infrastructure, which has made server-side plugin execution considerably more practical. In this article, we will look in order at why isolation is necessary, how it differs from traditional methods, and how to introduce it into custom development.
Why user code isolation is so difficult
The first point to recognize is that this is not merely a matter of "safe script execution." Code written by clients must be treated as potentially adversarial. Even without malicious intent, accidents happen regularly: exhausting CPU with infinite loops, allocating unbounded memory, or snooping on another tenant's files that happen to be readable. When delivering systems in custom development, "the client's code crashed the server" becomes a liability issue.
In short, what is required is an execution environment that satisfies three conditions simultaneously: first, blocking access to files, networks, and environment variables by default; second, capping CPU time, memory, and execution duration; and third, still maintaining lightweight startup and invocation capable of running hundreds of times per request. This third point makes isolation unexpectedly tricky.
Conventional isolation methods and their overhead
This kind of isolation has been attempted before. Reviewing the representative methods makes their respective strengths and weaknesses clear.
| Approach | Isolation strength | Startup overhead | Language flexibility | Drawbacks in custom development |
|---|---|---|---|---|
| Separate process + OS privilege restrictions | Medium | Medium | High | Privilege design is complex and easy to overlook things |
| Containers (gVisor, etc.) | Medium to high | Medium to high | High | Too heavy to run on a per-request basis |
| MicroVMs (Firecracker, etc.) | High | High | High | Startup is heavy, making it hard to increase density |
| In-language sandboxes (JS isolates, etc.) | Medium | Low | Low (effectively one language) | Numerous breakout cases; locked into a specific language |
| WASM + WASI | High | Low | High (multilingual) | Ecosystem is still evolving |
While containers and microVMs provide solid isolation, their startup overhead is too heavy for invoking a user function within a single API request. Keeping isolated runtimes lightweight is an industry-wide challenge, and striking the balance between lightness and isolation was likewise a focal point in Isolated Runtimes with Cloudflare Sandboxes (GH Media) and Isolated Execution with GKE Agent Sandbox (GH Media). WASM approaches this trade-off not at the language runtime level, but through the execution model itself.
The peace of mind in WASM being unable to do anything by default
What sets WASM isolation decisively apart is its adoption of capability-based security. A WASM module on its own cannot touch files, networks, or even the system clock. By default, it possesses no means of connecting to the outside world other than the interfaces (capabilities) explicitly provided by the host.
This works powerfully in custom development design. Traditional sandboxes follow the mindset of "closing dangerous gaps after the fact," meaning forgotten holes become vulnerabilities. WASM reverses this: "zero by default, pass only what is needed." For instance, if you only want to allow a client's plugin to run a function that takes an array of unit prices and returns a total, the host provides only the inputs and outputs for that function, omitting any file API. What is not provided cannot be accessed no matter how hard the code tries. This model where "it does not exist unless passed" makes trust boundary design remarkably straightforward.
What changed with WASI 0.3
WASI (WebAssembly System Interface) is the specification that standardizes those capabilities passed by the host. It defines shared interfaces for capabilities like files, clocks, random numbers, and networking, allowing WASM modules to use them while preserving portability.
According to reporting by Publickey, the biggest change in WASI 0.3, which reached its official release in 2026, is that asynchronous processing was incorporated into the common foundation of the WebAssembly Component Model. Previously, code handling asynchronous I/O and networking required runtime-specific conventions, making interoperability difficult. With async now part of the shared foundation in 0.3, writing asynchronous code that handles I/O and networking has become easier and interoperable across different implementations.
Applying this to real-world custom development, it means common business logic operations—such as calling external APIs or asynchronously awaiting database query results within user plugins—can now be written cleanly without being locked into a specific runtime. When building a plugin platform, whether asynchronous operations can be handled properly is a critical matter, making this standardization highly significant.
Cross-language composition with the Component Model
Another pillar is the WebAssembly Component Model. This is a mechanism for defining interfaces between the host and guest (plugin) using a typed interface language called WIT (WebAssembly Interface Type). Because interactions can be described using high-level types such as strings, records, lists, enums, and resources, developers are liberated from low-level conventions like "at this memory address, laid out like this..."
For example, an interface for a client plugin that takes a list of line items and returns the discounted total can be written in WIT as follows:
package gleamhub:pricing@0.1.0;
interface calculator {
record line-item {
name: string,
unit-price: u32,
quantity: u32,
}
// ホストが提供 / プラグインが実装する関数
calc-total: func(items: list<line-item>) -> u32;
}
world plugin {
export calculator;
}
As long as it satisfies this interface, the plugin can be written in Rust, Go, or any language that supports componentization. The host needs only to respect the type contract, allowing clients to implement logic in their preferred language. In custom development scenarios where "logic written by the client's development team runs on our platform," this language-agnostic composition proves invaluable. The host exposes only the desired functions via WIT; if file or network interfaces are not exposed, they remain invisible to the plugin.
How to apply resource limits
Even if you narrow down what can be done using capabilities, how much can be consumed is a separate question. WASM runtimes (such as Wasmtime) provide control points for this. For memory, you can set a cap on linear memory; for CPU, you can allocate an instruction execution budget called fuel and suspend execution once it is exhausted. Execution time itself can also be capped with epilogue timeouts.
Conceptually, the host reconfigures these limits prior to each invocation before launching the plugin.
// 概念例:呼び出しごとに予算を設定して実行
store.set_fuel(10_000_000)?; // CPU 予算(命令量)
store.limiter(|s| &mut s.limits); // メモリ上限などを束ねる
let total = calculator.call_calc_total(&mut store, &items)?;
Even if an infinite loop is written, it halts once fuel runs out, and memory hogging is blocked by the ceiling. Budgets can also be adjusted per tenant. The worst-case scenario where "client code crashes the server" is structurally prevented at the runtime level.
Custom development workflow — an extension mechanism for a business SaaS
Here is an example using a pseudonym. At "Company H," which operated an ordering SaaS for wholesalers, markup rates and rounding rules differed slightly for each customer. Previously, our team added conditional branches individually every time a customer made a request. Branches multiplied, creating a state of constant anxiety over whether changes for one company would impact another.
To solve this, we introduced a mechanism to plug in customer-specific price calculation plugins as WASM components. The approach was as follows: First, we defined a single price calculation interface in WIT, restricting what clients could touch strictly to line item inputs and outputs. Files and networks were not exposed at all. Next, we configured fuel and memory limits per invocation in Wasmtime, structurally preventing runaway processes. Plugins were promoted to production only after regression-testing calculation results against golden data in a staging environment, guaranteeing that changes in one tenant would not spill over to others.
As a result, customer-specific logic was decoupled from our core system, eliminating the mountain of conditional branches. Clients themselves (or our maintenance team) only needed to swap out plugins, reducing both lead times for modifications and incident risks. Furthermore, this design philosophy of "running locally on the client's machine" is directly continuous with the ideas discussed in Local-First and Offline Business Applications (GH Media), with WASM serving as a candidate execution runtime for it as well.
When to choose WASI, and when not to
To draw an honest line: WASI and the Component Model are not a panacea.
They are well-suited for cases where you want to isolate and execute untrusted or numerous small pieces of user logic in a lightweight, high-density manner. Typical examples include plugins, user-defined functions, tenant-specific custom logic, and lightweight edge processing. Conversely, they are not suited for cases where you want to port an entire large existing application to WASM, or make full use of rich OS features and GPUs. Those belong to the realm of containers and VMs.
We will also candidly point out the pitfalls. First, startup and invocation overhead: while light, it is not zero, so extremely high-frequency invocations require designing instance pooling and warming. Second, ecosystem maturity: componentization toolchains vary by language and are still developing. While Rust has robust support, other languages require advance verification. Third, debugging difficulty: stack traces and profiling inside WASM are not as seamless as native environments, requiring host-side logging design as a prerequisite. Decisions around this theme of lightweight isolation combined with high density demand the same line of judgment as designing elastic infrastructure, such as Next-Generation Search Infrastructure Using AWS OpenSearch Serverless (GH Media).
Conclusion — moving from shelving out of fear to embracing by design
The requirement to "run client code inside our SaaS" was historically shelved due to the heavy overhead of isolation. With WASI 0.3 bringing asynchronous processing into the common foundation, the Component Model enabling cross-language composition of typed interfaces, and runtimes enforcing resource limits—these three factors coming together make building a secure plugin infrastructure that "passes only required capabilities to code that can do nothing by default" a realistic option even in custom development.
At our company, we build SaaS extension mechanisms through custom development, covering everything from WIT interface design and Wasmtime resource limiting and operational design to plugin regression testing frameworks. If you want to safely run user-written logic, decouple a mountain of conditional branches, or assess whether WASM fits your company's requirements, please feel free to reach out via Contact.
Sources
- WASI 0.3 Reaches Official Release: WebAssembly Component Async Processing Becomes Part of the Common Foundation (Publickey)
- WebAssembly Official Website
- WASI Official Website
- Cloudflare Sandboxes Isolated Runtime (GH Media)
- GKE Agent Sandbox Isolated Execution (GH Media)
- Local-First and Offline Business Applications (GH Media)
- AWS OpenSearch Serverless Search Infrastructure (GH Media)









