受託でSaaSや業務システムを請けていると、必ず一度はこの相談が来ます。「お客様ごとに計算ロジックが微妙に違うので、画面から差し込めるようにしたい」「ノーコードの式入力では足りないので、ちょっとしたスクリプトを書かせたい」。要件としては自然なのですが、これを素直に実装しようとすると、お客様が書いたコードを自社のサーバー上で実行するという、かなり剣呑な話に化けます。eval で済ませれば一発でファイルもネットワークも握られますし、別プロセスやコンテナで隔離するのは運用が重い。だから「機能は欲しいが怖いので保留」のまま塩漬けになりがちです。
この「信頼できないコードを、安全に、でも軽量に動かしたい」という受託特有の悩みに、いま現実的な答えになりつつあるのが WebAssembly(WASM)と WASI です。2026年に入って WASI 0.3 が正式版になり、Component の非同期処理が共通基盤に組み込まれたことで、サーバーサイドでのプラグイン実行という用途が一段使いやすくなりました。本記事では、なぜ隔離が要るのか、従来手段と何が違うのか、そして受託でどう載せていくかを順に見ていきます。
なぜ「ユーザーコードの隔離」がこんなに難しいのか
まず確認したいのは、これが単なる「安全なスクリプト実行」の話ではない、という点です。お客様の書いたコードは潜在的に敵対的だと見なさなければいけません。悪意がなくても、無限ループでCPUを食い潰す、メモリを際限なく確保する、たまたま読めてしまった他テナントのファイルを覗く——こうした事故は普通に起きます。受託で引き渡す以上、「お客様のコードがサーバーを落とした」では責任問題になります。
つまり必要なのは、次の3つを同時に満たす実行環境です。ひとつ、ファイル・ネットワーク・環境変数へのアクセスを既定で遮断できること。ふたつ、CPU時間・メモリ・実行時間に上限をかけられること。みっつ、それでいて起動と呼び出しが軽く、リクエストごとに何百回も実行できること。この3点目があるせいで、隔離は思いのほか厄介になります。
従来の隔離手段とその「重さ」
この手の隔離は、これまでもやられてきました。代表的な手段を整理すると、それぞれ得手不得手がはっきりしています。
| 手段 | 隔離の強さ | 起動コスト | 言語の自由度 | 受託での難点 |
|---|---|---|---|---|
| 別プロセス + OS権限制限 | 中 | 中 | 高 | 権限設計が煩雑、取りこぼしやすい |
| コンテナ(gVisor等) | 中〜高 | 中〜高 | 高 | リクエスト単位で回すには重い |
| マイクロVM(Firecracker等) | 高 | 高 | 高 | 起動が重く、密度を上げにくい |
| 言語内サンドボックス(JS isolate等) | 中 | 低 | 低(実質1言語) | 脱獄事例が多く、言語が固定される |
| WASM + WASI | 高 | 低 | 高(多言語) | エコシステムが発展途上 |
コンテナやマイクロVMは隔離としては堅いのですが、「ひとつのAPIリクエストの中でユーザー関数を呼ぶ」という用途には起動が重すぎます。隔離ランタイムをどう軽く保つかは業界全体のテーマで、Cloudflare Sandboxes による隔離ランタイム(GH Media) や GKE Agent Sandbox の隔離実行(GH Media) でも、同じ「軽さと隔離の両立」が論点になっていました。WASM はこの両立を、言語ランタイムのレベルではなく実行モデルそのもので解こうとするアプローチです。
WASMの「既定で何もできない」という安心感
WASM の隔離が他と決定的に違うのは、ケイパビリティベースのセキュリティを採っている点です。WASM モジュールは、それ単体ではファイルもネットワークも時計すら触れません。ホスト側が明示的に渡したインターフェース(ケイパビリティ)以外、外の世界とつながる手段を一切持たないのが既定状態です。
これは受託の設計で効きます。従来のサンドボックスは「危ないものを後から塞ぐ」発想なので、塞ぎ忘れが脆弱性になりました。WASM は逆で、「既定でゼロ、必要なものだけ渡す」。たとえばお客様のプラグインに「単価の配列を受け取って合計を返す関数」だけ許したいなら、ホストはその関数の入出力だけを渡し、ファイルAPIは渡しません。渡していないものは、コード側がどう頑張っても触れない。この「渡さなければ存在しない」というモデルが、信頼境界の設計を驚くほど単純にしてくれます。
WASI 0.3で何が変わったか
WASI(WebAssembly System Interface)は、その「ホストが渡すケイパビリティ」を標準化した規格です。ファイル・時計・乱数・ネットワークといった機能を、WASM モジュールが移植性を保ったまま使えるよう、共通のインターフェースとして定義しています。
Publickey の報道によれば、2026年に正式版となった WASI 0.3 の最大の変化は、非同期処理が WebAssembly Component の共通基盤に組み込まれたことです。これまで非同期I/Oやネットワークを扱うコードは、ランタイムごとに独自の作法が必要で相互運用が難しかったのですが、0.3 で非同期が共通基盤の一部になったことで、I/Oやネットワークを扱う非同期コードが書きやすく、かつ実装をまたいで相互運用しやすくなりました。
受託の実務に引き付けると、これは「ユーザープラグインの中で外部APIを叩く」「DBアクセス結果を非同期で待つ」といった、実際の業務ロジックでありがちな処理が、特定ランタイムに縛られず素直に書けるようになった、ということです。プラグイン基盤を作るうえで、非同期がまともに扱えるかどうかは死活問題なので、ここが共通化された意味は小さくありません。
Component Modelで「言語をまたいで」合成する
もうひとつの柱が WebAssembly Component Model です。これはホストとゲスト(プラグイン)の間のインターフェースを、WIT(WebAssembly Interface Type)という型付きの言語で定義する仕組みです。文字列・レコード・リスト・enum・リソースといった高水準の型でやり取りを記述できるため、「メモリのこのアドレスにこういうレイアウトで……」という低レベルの取り決めから解放されます。
たとえば、お客様プラグインに「明細リストを渡して割引後の合計を返してもらう」インターフェースは、WIT でこう書けます。
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;
}
このインターフェースを満たしさえすれば、プラグインの中身は Rust でも Go でも、コンポーネント化に対応した言語なら何で書かれていても構いません。ホスト側は型の契約だけを見ればよく、お客様は得意な言語で実装できる。受託で「お客様の開発チームが書いたロジックを、うちの基盤に載せる」というシナリオでは、この言語非依存の合成が効いてきます。ホストは渡したい関数だけを WIT で公開し、ファイルやネットワークのインターフェースは公開しなければ、プラグインからは見えません。
リソース制限をどうかけるか
ケイパビリティで「何ができるか」を絞っても、「どれだけ使えるか」は別の話です。WASM ランタイム(Wasmtime など)は、ここに対する制御点を持っています。メモリは線形メモリの上限を設定し、CPU はフューエル(fuel)と呼ばれる命令実行量の予算を割り当てて、使い切ったら中断させられます。実行時間そのものにもエピローグでタイムアウトをかけられます。
概念だけ示すと、ホスト側は呼び出しのたびにこうした上限を再設定してからプラグインを起動します。
// 概念例:呼び出しごとに予算を設定して実行
store.set_fuel(10_000_000)?; // CPU 予算(命令量)
store.limiter(|s| &mut s.limits); // メモリ上限などを束ねる
let total = calculator.call_calc_total(&mut store, &items)?;
無限ループを書かれてもフューエルが尽きれば止まり、メモリ爆食いも上限で弾かれる。テナントごとに予算を変えることもできます。「お客様のコードがサーバーを落とす」という最悪のシナリオを、ランタイムのレベルで構造的に防げるわけです。
受託での進め方 ― ある業務SaaSの拡張機構
仮名で一例を挙げます。卸売業向けの受発注SaaSを運用していた「H社」では、得意先ごとに掛け率や端数処理のルールが少しずつ違い、これまではお客様の要望が来るたびに当社が個別に条件分岐を追加していました。分岐は膨れ上がり、一社の都合の変更が別の社に影響しないか毎回ヒヤヒヤする状態でした。
ここに、得意先ごとの「価格計算プラグイン」をWASMコンポーネントとして差し込める仕組みを入れました。進め方はこうです。まずWITで価格計算インターフェースを1本だけ定義し、お客様側が触れるのは明細の入出力だけに固定。ファイル・ネットワークは一切公開しません。次にWasmtimeでフューエルとメモリ上限を呼び出し単位で設定し、暴走を構造的に封じました。プラグインは検証環境でゴールデンデータに対する計算結果を回帰テストしてから本番に昇格させ、1テナントの変更が他テナントに波及しないことを保証しました。
結果として、当社のコア部分から得意先固有のロジックが切り離され、条件分岐の山が消えました。お客様自身(あるいは当社の保守チーム)がプラグインだけ差し替えればよくなり、改修のリードタイムも事故リスクも下がりました。なお、こうした「お客様の手元でも動く」設計思想は ローカルファースト/オフライン業務アプリ(GH Media) で扱った考え方とも地続きで、WASM はその実行基盤としても候補になります。
いつWASIを選び、いつ選ばないか
誠実に線引きしておきます。WASI/Component Model は万能ではありません。
向くのは、信頼できない、または多数の小さなユーザーロジックを、軽量・高密度に隔離実行したいケースです。プラグイン、ユーザー定義関数、テナント別カスタムロジック、エッジでの軽量処理あたりが典型です。逆に向かないのは、既存の大規模アプリを丸ごとWASM化したい、リッチなOS機能やGPUをフルに使いたい、といったケース。そこはコンテナやVMの領分です。
落とし穴も正直に書きます。第一に起動・呼び出しのオーバーヘッドは、軽いとはいえゼロではないので、超高頻度な呼び出しではインスタンスのプールやウォーム化の設計が要ります。第二にエコシステムの成熟度で、言語ごとのコンポーネント化ツールチェーンはまだ発展途上です。Rust は手厚いものの、言語によっては事前検証が要ります。第三にデバッグの難しさで、WASM内部のスタックトレースやプロファイリングはネイティブほど快適ではなく、ホスト側のロギング設計で補う前提を置くべきです。このあたりの「軽量隔離 × 高密度」というテーマは、AWS OpenSearch Serverless を使った次世代検索基盤(GH Media) のような、需要に応じて伸縮するインフラ設計と同じ筋の判断が求められます。
まとめ ― 「怖いから保留」を「設計で引き受ける」へ
「お客様のコードをうちのSaaSで動かしたい」という要求は、これまで隔離の重さゆえに保留されがちでした。WASI 0.3 で非同期処理が共通基盤に入り、Component Model で型付きインターフェースを言語横断で合成でき、ランタイムでリソース上限をかけられる——この3点が揃ったことで、「既定で何もできないコードに、必要なケイパビリティだけ渡す」という安全なプラグイン基盤が、受託でも現実的に組めるようになりました。
当社では、WITによるインターフェース設計、Wasmtimeでのリソース制限と運用設計、プラグインの回帰テスト体制までを含めて、SaaSの拡張機構を受託で構築しています。「ユーザーが書いたロジックを安全に載せたい」「いまの条件分岐の山を切り離したい」「WASMが自社の要件に向くか診断してほしい」というご相談は お問い合わせ からお気軽にどうぞ。