MicroPython と WASM を用いたサンドボックス環境での Python コード実行
Simon Willison は、MicroPython と WASM を組み合わせた新しいサンドボックス環境「micropython-wasm」を公開し、Python プラグインの安全な実行とリソース制限の実現を目指している。
キーポイント
WASM による Python サンドボックスの実現
MicroPython を WebAssembly (WASM) で動作させることで、従来の Python プラグインシステムが抱えるセキュリティリスクを解消する新しいアプローチを発表した。
厳格なリソース制限と依存関係の管理
メモリと CPU 使用量に上限を設定し、PyPI から依存関係をクリーンにインストールできる仕組みにより、無限ループやリソース枯渇を防ぐ。
Datasette エコシステムへの統合
この技術は「datasette-agent-micropython」として Datasette Agent のプラグインとして実装され、データ変換やスケジュール実行などでの安全なコード実行を可能にする。
影響分析・編集コメントを表示
影響分析
この発表は、Python エコシステムにおいて「柔軟性」と「安全性」を両立させるための重要な技術的マイルストーンです。特にオープンソースプロジェクトがプラグインシステムを採用する際、セキュリティ懸念からコード実行機能への導入を躊躇していたケースに対して、WASM を活用した実用的な解決策を示すことで、より安全な拡張機能の開発を促進すると考えられます。
編集コメント
Simon Willison 氏による長年の課題解決への挑戦であり、特に Python プラグインのセキュリティ実装において Wasm の活用は注目すべき動きです。
私は数年にわたり、サンドボックス内でコードを実行するためのさまざまなアプローチを試してきましたが、最新の試みは私が長年求めていたすべての特徴を備えているように感じられます。これを micropython-wasm というアルファ版パッケージとしてリリースし、Datasette Agent 向けのコード実行サンドボックスプラグインである datasette-agent-micropython で使用しています。
なぜサンドボックスが必要なのか
私の主要なオープンソースプロジェクト - Datasette、LLM、さらには sqlite-utils - はすべてプラグインをサポートしています。
私はソフトウェアを拡張するためのメカニズムとしてプラグインを心から愛しています。慎重に設計されたプラグインシステムは、新しいことに挑戦する際のリスクをほぼゼロまで減らします。どんなに荒廃したアイデアでも、コアアプリケーション自体に永続的な影響を与えることはありません。私のソフトウェアは一夜にして新機能を追加でき、プルリクエストのレビューさえ不要です!
しかし、大きな欠点があります。私のプラグインシステムはすべて Python と Pluggy を使用しており、プラグインコードはアプリケーション内で完全な特権を持って実行されます。バグのあるプラグインや悪意のあるプラグインがすべての機能を破損させたり、プライベートデータを漏洩させたりする可能性があります。
プラグイン形式のコードを、承認されていないファイルの読み込みやネットワーク接続ができず、アプリケーション全体やユーザーのコンピュータに対してリスクや害を与えるような動作ができない環境で実行できればと思います。
私の関心はプラグインだけに留まりません。特に Datasette においては、任意のコードの実行が有用となる多くの機能をサポートしたいと考えています。すでに Datasette Enrichments ではこの実験を行っており、ここではテーブルに保存された値を変換するためにコードを使用できます。私は、スケジュールに従ってコードを実行し、承認された場所から JSON を取得し、それを辞書のリスト形式に変換するごく小さなコードを実行した後、その結果を SQLite データベースのテーブルに行として挿入する仕組みを構築したいと考えています。
サンドボックスから私が求めるもの
私の目標は、自分の Python アプリケーション内でコードを安全に実行することです。ここで必要となるのは以下の通りです:
- PyPI からクリーンにインストールできる依存関係、必要に応じて複数のプラットフォームに対応したバイナリホイールを含む。私のソフトウェアを利用する人が、私の Python パッケージを直接インストールする以外に追加の手順を余儀なくされることは望まない。
- 実行されたコードはメモリ制限と CPU 制限の両方を適用されるべきである。while True: s += "longer string" のようなコードが私のアプリケーションやユーザーのコンピュータをクラッシュさせることを防ぎたい。
- ファイルアクセスは厳格に制御されなければならない。ファイルシステムへのアクセスを全く許可しないか、あるいはどのファイルを参照でき、どのファイルに書き込み可能かを私が完全に定義できるようにする必要がある。
- ネットワークアクセスも同様に制御されるべきである。サンドボックス化されたコードは、私が完全に制御するレイヤーを経由せずに他の任何东西と通信できないようにする必要がある。
- ホスト関数との相互作用のサポートが必要である。実行されているコードに慎重に特定のプラットフォーム機能を公開できない場合、サンドボックスはあまり役に立たない。
- 堅牢で、サポートが提供され、明確に文書化されているものでなければならない。メンテナンスが活発ではないことを警告するリポジトリで見かけたサンドボックスプロジェクトの数を数えきれなくなっている!
WebAssembly はここで非常に有望に見える
Web ブラウザは、悪意のあるコードに関しては想像できる中で最も過酷な環境で動作している。彼らの仕事は、ほぼすべてのページ読み込み時にウェブから信頼できないコードをダウンロードして実行することである。
これを踏まえると、JavaScript エンジンはサンドボックスの優れた候補となります。残念ながら、これらのエンジンは非常に複雑であり、他のプロジェクトに容易に埋め込むことを目的として設計されていません。私が目にした v8-in-Python のプロジェクトの多くはメンテナンス頻度が低く、完全に信頼できないコードとの併用には警告付きです。
WebAssembly は*はるかに優れた*候補です。これは、私が重視するすべての特性をサポートするように最初から設計され、ほぼ 10 年にわたりブラウザでテストされてきました。wasmtime Python ライブラリは積極的にメンテナンスされており、バイナリホイールも提供されています。
MicroPython in WebAssembly
wasmtime などの WebAssembly エンジンは WebAssembly バイナリを実行します。Rust のような一部のプログラミング言語は WebAssembly に直接コンパイルしやすいですが、JavaScript や Python のような動的言語はより困難です。これらは eval() などの言語プリミティブをサポートしており、ランタイム時に完全なインタプリタが必要となるためです。
Python を実行するには、WebAssembly にコンパイルされた完全な Python インタプリタが必要であり、コードを供給しやすくし、ホスト関数をフックし、結果にアクセスできるように接続する必要があります。
Pyodide はブラウザ内で WebAssembly を使用して Python を実行するための優れたパッケージを提供していますが、サーバーサイドの Python で Pyodide を使用するサポートはされていません。私が確認できた最新のアドバイスは 2024 年 10 月 のもので、「Pyodide は Emscripten ツールチェーンによって構築されており、ブラウザまたは Node.js でのみ実行可能である」と述べています。
先日、私はこれを別の選択肢として MicroPython を見てみることにしました。MicroPython のサイトには以下のように記載されています:
MicroPython は、Python 3 プログラミング言語の軽量で効率的な実装であり、Python 標準ライブラリの小さなサブセットを含み、マイクロコントローラーや制約のある環境での実行に最適化されています。
WebAssembly は私にとってまさに制約のある環境のように感じられます!
バージョン 1 の構築
私は GPT-5.5 Pro に 調査を依頼 したところ、Yamamoto Takahashi による「Experimental WASI support for ports/unix」と題された MicroPython に対する PR がヒットしました。
その後、この research.md ドキュメント が生成されたため、Codex Desktop と GPT-5.5 に 任せて 何が起きるか見てみました:
research.md ドキュメントを読み、これを実装してください。このプロジェクトの一環として、カスタム WASM 版の MicroPython をコンパイルするスクリプトを書く必要があるでしょう。その際、MicroPython のコードを /tmp ディレクトリにフェッチする必要があります。
動作しました。これで、WebAssembly サンドボックス内で Python コードを実行できるプロトタイプの Python ライブラリが完成しました!
最も難しかった課題は、永続的なインタプリタ状態の維持でした。ここで使用している WASM ビルドは、インタプリタを開始しコードを実行した後、最後にインタプリタを停止する単一のエントリーポイントしか暴露していません。
これはワンオフのスクリプトには問題なく機能しますが、Datasette Agent では変数や関数がメモリ上に残存し、複数のコード実行呼び出し間で再利用できるようにしたいと考えています。
コーディングエージェントと作業することの素晴らしい点は、アイデアから概念実証(Proof of Concept)までを迅速に実現できることです。私は以下のようにプロンプトしました:
変数を定着させるにはどうすればよいでしょうか?MicroPython 内部でコードを実行し、ホスト関数 get_next_python_code() を呼び出してその結果を eval() に渡すというアイデアはどうか。そしてこのホスト関数は、キューを持つスレッドで実行されるなどして、新しいコードが利用可能になるまでブロックする。このような考え方や類似のアイデアがここで役立つでしょうか?
いくつかの試行錯誤の後、動作するバージョンにたどり着きました!Python コードでは、今や以下のように記述できます:
from micropython_wasm import MicroPythonSession
with MicroPythonSession() as session:
print(session.run("x = 10\nprint(x)").stdout)
print(session.run("x += 5\nprint(x)").stdout)
print(session.run("print(x * 2)").stdout)
Under the hood this starts a thread, sets up a request queue and then sends messages to that queue for the session.run() command, each time waiting on a reply queue for the result of that execution. Inside WASM the MicroPython interpreter blocks waiting for a __session_next__() host function to return the next line of code, which it runs eval() on before calling __session_result__({"id": request_id, "ok": True}) when each block has been successfully executed.
The other piece of complexity was supporting host functions, so my Python library could selectively expose functions that could then be called by code running in MicroPython.
Codex ended up solving this with 78 lines of C, which ends up compiled into the 362KB WebAssembly blob I'm distributing with the package.
I am by no means a C programmer, but I've read the C and had two different models explain it to me (here's Claude's explanation) and I've subjected it to a barrage of tests.
WebAssembly で作業する素晴らしい点は、C のコードに致命的な欠陥があった場合でも、最悪の事態は WebAssembly 実行が例外で失敗することだけだということ。私はそのリスクなら許容できます。
メモリ制限は wasmtime で直接サポートされています。CPU 制限は少し難しいですが、wasmtime は「燃料 (fuel)」という概念を提供しており、WebAssembly 呼び出しが実行できる操作の数を制限するために使用できます。これはこの問題に適切な解決策ですが、その単位を推論するのは困難です。現在、デフォルトの「燃料」設定として 2000 万を試験中ですが、これが最も適切な値であるという確信はまだ持てていません。
私のバイブスコーディングしたサンドボックスを信頼すべきか?
未熟で緩やかにしかメンテナンスされていないサンドボックスライブラリについて不満を述べた直後に、なんと自分自身でそれを作ってしまったとは皮肉なものです!
私はあえてアルファ版のリリースバージョンとして公開しており、大きなリスクを負う覚悟のある方以外にはまだ誰にも推奨する準備はできていません。
私自身が使用する分には問題ない程度には十分なテストを行ってきました。これを使用した最初のプラグイン datasette-agent-micropython をリリース済みです。また、GPT-5.5 xhigh をその Datasette Agent プラグインに固定し、サンドボックスから脱出するよう挑戦しましたが、これまでに脱出には成功していません。
この実装が、専門的なセキュリティチームを有し、重大な課題に直面しているいくつかの企業に対して、WebAssembly における Python の利用をサンドボックス化のアプローチとして採用し、独自のソリューションをオープンソース化するよう説得できることを願っています。
タグ: python, sandboxing, ai, datasette, generative-ai, llms, ai-assisted-programming, codex, datasette-agent, micropython
原文を表示
I've been experimenting with different approaches to running code in a sandbox for several years now, but my latest attempt feels like it might finally have all of the characteristics I've been looking for. I've released it as an alpha package called micropython-wasm, and I'm using it for a code execution sandbox plugin for Datasette Agent called datasette-agent-micropython.
Why do I want a sandbox?
My key open source projects - Datasette, LLM, even sqlite-utils - all support plugins.
I absolutely love plugins as a mechanism for extending software. A carefully designed plugin system reduces the risk involved in trying new things to almost nothing - even the wildest ideas won't leave a lasting influence on the core application itself. My software can grow a new feature overnight and I don't even have to review a pull request!
There's one major drawback: my plugin systems all use Python and Pluggy, and plugin code executes with full privileges within my applications. A buggy or malicious plugin could break everything or leak private data.
I'd love to be able to run plugin-style code in an environment where it is unable to read unapproved files, connect to a network, or generally operate in a way that's risky or harmful to the rest of the application or the user's computer.
My interest covers more than just plugins. For Datasette in particular there are many features I'd like to support where arbitrary code execution would be useful. I've already experimented with this for Datasette Enrichments, where code can be used to transform values stored in a table. I'd love to build a mechanism where you can run code on a schedule that fetches JSON from an approved location, runs a tiny bit of code to reformat it into a list of dictionaries, then inserts those as rows in a SQLite database table.
What I want from a sandbox
My goal is to execute code safely within my own Python applications. Here's what I need:
- Dependencies that cleanly install from PyPI, including binary wheels across multiple platforms if necessary. I don't want people using my software to have to take any extra steps beyond directly installing my Python package.
- Executed code must be subject to both memory and CPU limits. I don't want while True: s += "longer string" to crash my application or the user's computer.
- File access must be strictly controlled. Either no filesystem access at all or I get to define exactly which files can be read and which files can be written to.
- Network access is controlled as well. Sandboxed code should not be able to communicate with anything without going through a layer I fully control.
- Support for interaction with host functions. A sandbox isn't much use if I can't carefully expose selected platform features to the code that it's running.
- It has to be robust, supported, and clearly documented. I've lost count of the number of sandbox projects I've seen in repos with warnings that they aren't actively maintained!
WebAssembly looks really promising here
Web browsers operate in the most hostile environment imaginable when it comes to malicious code. Their job is to download *and execute* untrusted code from the web on almost every page load.
Given this, JavaScript engines should be excellent candidates for sandboxes. Sadly those engines are also extremely complicated, and are not designed for easy embedding in other projects. Most of the v8-in-Python projects I've seen are infrequently maintained and come with warnings not to use them with completely untrusted code.
WebAssembly is a *much better* candidate. It was designed from the start to support all of the characteristics I care about and has been tested in browsers for nearly a decade. The wasmtime Python library is actively maintained and has binary wheels.
MicroPython in WebAssembly
WebAssembly engines like wasmtime run WebAssembly binaries. Some programming languages like Rust are easy to compile directly to WebAssembly. Dynamic languages like JavaScript and Python are harder - they support language primitives like eval(), which means they need a full interpreter available at runtime.
To run Python we need a full Python interpreter compiled to WebAssembly, wired up in a way that makes it easy to feed it code, hook up host functions and access the results.
Pyodide offers an outstanding package for running Python using WebAssembly in the browser, but using Pyodide in server-side Python isn't supported. The most recent advice I could find was from October 2024 stating "Pyodide is built by the Emscripten toolchain and can only run in a browser or Node.js".
The other day I decided to take a look at MicroPython as an option for this. The MicroPython site says:
MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.
WebAssembly sure feels like a constrained environment to me!
Building the first version
I had GPT-5.5 Pro do some research for me, which turned up this PR against MicroPython by Yamamoto Takahashi titled "Experimental WASI support for ports/unix".
It then produced this research.md document, so I let Codex Desktop and GPT-5.5 high loose on it to see what would happen:
read the research.md document and build this. You will probably need to write a script that compiles a custom WASM version of MicroPython as part of this project - fetch the MicroPython code to a /tmp directory for this as part of that script.
It worked. I now had a prototype Python library that could execute Python code inside a WebAssembly sandbox!
The trickiest piece to solve was persistent interpreter state. The WASM build we are using here exposes a single entry point which starts the interpreter, runs the code and then stops the interpreter at the end.
This works fine for one-off scripts, but for Datasette Agent I want variables and functions to stay resident in memory so I can reuse them across multiple code execution calls.
A neat thing about working with coding agents is that you can get from an idea to a proof of concept quickly. I prompted:
For keeping variables resident: what if we ran code inside micropython itself which called a host function get_next_python_code() and then passed that to eval() - and that host function blocked until new code was available, maybe by running in a thread with a queue? Could that or a similar idea help here?
After some iteration we got to a version of this that works! In Python code you can now do this:
from micropython_wasm import MicroPythonSession
with MicroPythonSession() as session:
print(session.run("x = 10\nprint(x)").stdout)
print(session.run("x += 5\nprint(x)").stdout)
print(session.run("print(x * 2)").stdout)Under the hood this starts a thread, sets up a request queue and then sends messages to that queue for the session.run() command, each time waiting on a reply queue for the result of that execution. Inside WASM the MicroPython interpreter blocks waiting for a __session_next__() host function to return the next line of code, which it runs eval() on before calling __session_result__({"id": request_id, "ok": True}) when each block has been successfully executed.
The other piece of complexity was supporting host functions, so my Python library could selectively expose functions that could then be called by code running in MicroPython.
Codex ended up solving this with 78 lines of C, which ends up compiled into the 362KB WebAssembly blob I'm distributing with the package.
I am by no means a C programmer, but I've read the C and had two different models explain it to me (here's Claude's explanation) and I've subjected it to a barrage of tests.
The great thing about working with WebAssembly is that if the C turns out to be fatally flawed the worst that can happen is the WebAssembly execution will fail with an exception. I can live with that risk.
Memory limits are directly supported by wasmtime. CPU limits are a little harder: wasmtime offers a "fuel" concept to limit how many operations a WebAssembly call can execute, and that's the correct fit for this problem, but the units are hard to reason about. I'm experimenting with a 20 million default "fuel" setting now but I'm not confident that it's the most appropriate value.
Should you trust my vibe-coded sandbox?
Having complained about immature, loosely-maintained sandboxing libraries, it's deeply ironic that I've now built my own!
I deliberately slapped an alpha release version on it, and I'm not ready to recommend it to anyone who isn't willing to take a significant risk.
I've put it through enough testing that I'm OK using it myself. I've shipped my first plugin that uses it, datasette-agent-micropython. I've also locked GPT-5.5 xhigh in that Datasette Agent plugin and challenged it to break out of the sandbox and so far it has not managed to.
I'm hoping this implementation can convince some companies with professional security teams and high-stakes problems to commit to using Python in WebAssembly as a sandboxing approach and open source their own solutions.
Tags: python, sandboxing, ai, datasette, generative-ai, llms, ai-assisted-programming, codex, datasette-agent, micropython
関連記事
Datasette Agent MicroPython 0.1a0 のリリース
Simon Willison が、GPT-5.5 を使用して Python コードを安全に生成・実行する「Datasette Agent」のアルファ版「datasette-agent-micropython 0.1a0」を発表し、サンドボックスからの脱出を試みる攻撃が失敗したと報告しました。
スキル.sh API の提供開始
Vercel が、オープンソースエコシステムから 60 万種以上のスキル情報を検索・取得できる「skills.sh API」を正式に公開した。プロジェクトごとの OIDC トークンによる認証で利用可能となる。
コード参照ハッチの防御(GitHub リポジトリ)
Anthropic は、Claude を用いた自律的な脆弱性発見と修正のためのリファレンス実装を GitHub に公開し、一般ベストプラクティスに基づくカスタムパイプライン構築を可能にした。