使用MicroPython和WASM在沙盒中运行Python代码
摘要
Simon Willison介绍了micropython-wasm,一个alpha版本包,它使用编译为WebAssembly的MicroPython在沙盒中运行Python代码,从而在Datasette等Python应用中实现安全的插件执行。
暂无内容
查看缓存全文
缓存时间: 2026/06/08 03:31
# 在沙箱中用 MicroPython 和 WASM 运行 Python 代码
来源:https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/
2026 年 6 月 6 日
我多年来一直在尝试不同的沙箱代码运行方法,但最近这次尝试感觉终于具备了所有我一直在寻找的特性。我以 alpha 包的形式发布了它,名为 `micropython-wasm`(https://github.com/simonw/micropython-wasm),并在 Datasette Agent(https://github.com/datasette/datasette-agent)的一个代码执行沙箱插件 `datasette-agent-micropython`(https://github.com/datasette/datasette-agent-micropython)中使用了它。
- [为什么我需要一个沙箱?](#为什么我需要一个沙箱?)
- [我对沙箱的要求](#我对沙箱的要求)
- [WebAssembly 看起来非常有前途](#webassembly-看起来非常有前途)
- [WebAssembly 中的 MicroPython](#webassembly-中的-micropython)
- [构建第一个版本](#构建第一个版本)
- [自己试试](#自己试试)
- [你应该信任我的“氛围编码”沙箱吗?](#你应该信任我的氛围编码沙箱吗?)
#### 为什么我需要一个沙箱?
我的核心开源项目——Datasette(https://datasette.io/)、LLM(https://llm.datasette.io/)、甚至 sqlite-utils(https://sqlite-utils.datasette.io/)——都支持插件。
我非常喜欢插件这种扩展软件的机制。一个精心设计的插件系统可以将尝试新事物的风险降到几乎为零——即使是最疯狂的想法也不会对核心应用程序产生持久影响。我的软件可以一夜之间长出新功能,而我甚至不需要审查一个 pull request!
但有一个主要缺点:我的插件系统都使用 Python 和 Pluggy(https://pluggy.readthedocs.io/en/latest/),并且插件代码在我的应用程序中以完全权限执行。一个有 bug 或恶意的插件可能会搞垮一切或泄露私人数据。
我希望能够在一个无法读取未经批准的文件、无法连接网络、或者以任何对应用程序其余部分或用户计算机有风险或有害的方式运行插件代码的环境中执行插件式代码。
我的兴趣不仅仅局限于插件。特别是对于 Datasette,有很多功能我希望支持,而这些功能会用到任意代码执行。我已经在 Datasette Enrichments(https://enrichments.datasette.io/)中对此进行了实验,其中可以使用代码来转换存储在表中的值。我希望构建一种机制,让你可以按计划运行代码,从批准的地址获取 JSON,运行一小段代码将其重新格式化为字典列表,然后将这些字典作为行插入到 SQLite 数据库表中。
#### 我对沙箱的要求
我的目标是在我自己的 Python 应用程序中安全地执行代码。以下是需要满足的条件:
- 依赖项要能**从 PyPI 干净地安装**,必要时包括跨多个平台的二进制 wheel。我不希望使用我软件的人需要采取任何额外步骤,而不仅仅是直接安装我的 Python 包。
- 执行的代码必须同时受到**内存**和 **CPU** 限制。我不希望 `while True: s += "longer string"` 导致我的应用程序或用户计算机崩溃。
- **文件访问必须严格控制**。要么完全没有文件系统访问,要么我可以定义哪些文件可读、哪些文件可写。
- **网络访问也要受到控制**。沙箱中的代码应该无法通过我不完全控制的层与任何东西通信。
- 支持与**宿主函数**交互。如果我无法向运行的代码精心暴露选定的平台特性,那沙箱就没多大用处。
- 它必须**健壮、有支持、文档清晰**。我已经记不清有多少沙箱项目的仓库里带有“未积极维护”的警告了!
#### WebAssembly 看起来非常有前途
Web 浏览器是在最恶意环境中运行的,尤其是面对恶意代码时。它们在几乎每次页面加载时都要从网络下载*并执行*不受信任的代码。
因此,JavaScript 引擎应该是沙箱的优秀候选。但遗憾的是,这些引擎极其复杂,并且不适合轻松嵌入到其他项目中。我见过的大多数 V8-in-Python 项目维护不频繁,并带有警告说不要用于完全不受信任的代码。
WebAssembly 是一个**更好的**候选方案。它从一开始就被设计来支持我在意的所有特性,并且已经在浏览器中测试了近十年。`wasmtime`(https://pypi.org/project/wasmtime)Python 库将 WASM 带到了 Python 中,它正在积极维护,并且有二进制 wheel。
#### WebAssembly 中的 MicroPython
像 wasmtime 这样的 WebAssembly 引擎运行 WebAssembly 二进制文件。有些编程语言(如 Rust)可以轻松直接编译为 WebAssembly。动态语言(如 JavaScript 和 Python)则更难——它们支持像 `eval()` 这样的语言原语,这意味着它们在运行时需要一个完整的解释器。
要运行 Python,我们需要一个完整的 Python 解释器编译为 WebAssembly,并以一种便于向其输入代码、挂钩宿主函数并访问结果的方式连接起来。
Pyodide 提供了一个出色的包,可以在浏览器中使用 WebAssembly 运行 Python,但不支持在服务器端 Python 中使用 Pyodide。我能找到的最新建议是 2024 年 10 月(https://github.com/pyodide/pyodide/discussions/5145)的,其中说“Pyodide 由 Emscripten 工具链构建,只能在浏览器或 Node.js 中运行。”
几天前,我决定看看 MicroPython(https://micropython.org/)作为这个问题的选项。MicroPython 网站上说:
> MicroPython 是 Python 3 编程语言的一种精简高效实现,包含一小部分 Python 标准库,并针对微控制器和受限环境进行了优化。
对我来说,WebAssembly 确实感觉像一个受限环境!
#### 构建第一个版本
我让 GPT-5.5 Pro 做了一些研究(https://chatgpt.com/share/6a1e2a5c-58b8-8328-ba1c-0e6aadb0a051),它找到了 MicroPython 上的这个 PR(https://github.com/micropython/micropython/pull/13676),由 Yamamoto Takahashi(https://github.com/yamt)提交,标题是“Experimental WASI support for ports/unix”。
然后它生成了这个 research.md 文档(https://github.com/simonw/micropython-wasm/blob/c08fbd2276b15dc8c9bdff82845f750971f45647/research.md),所以我让 Codex Desktop 和 GPT-5.5 high 随意发挥(https://gist.github.com/simonw/27461a16d76f28f8619c609444d544fe)看看会发生什么:
> `阅读 research.md 文档并构建这个。你可能需要编写一个脚本,作为该项目的一部分编译一个自定义 WASM 版本的 MicroPython——作为该脚本的一部分,将 MicroPython 代码获取到 /tmp 目录。`
成功了。我现在有了一个原型 Python 库,可以在 WebAssembly 沙箱内执行 Python 代码!
最难解决的问题是持久的解释器状态。我们这里使用的 WASM 构建暴露了一个单一的入口点,该入口点启动解释器,运行代码,然后停止解释器。
这对于一次性脚本来说很好,但对于 Datasette Agent,我希望变量和函数能驻留在内存中,以便在多次代码执行调用之间重用它们。
与编码代理合作的一个好处是,你可以快速从想法到概念验证。我提示:
> `为了保持变量驻留:如果我们在 MicroPython 内部运行代码,调用一个宿主函数 get_next_python_code(),然后将其传递给 eval()——那么宿主函数会阻塞,直到新代码可用,也许通过在一个带有队列的线程中运行?这样或类似的思路能帮上忙吗?`
经过一些迭代,我们得到了一个有效的版本!现在在 Python 代码中你可以这样做:
```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)
```
在底层,它启动一个线程,设置一个请求队列,然后向该队列发送消息以执行 `session.run()` 命令,每次等待回复队列以获取该执行的结果。在 WASM 内部,MicroPython 解释器阻塞等待一个 `__session_next__()` 宿主函数返回下一行代码,然后对其运行 `eval()`,每次成功执行一个代码块后调用 `__session_result__({"id": request_id, "ok": True})`。
另一个复杂之处是支持宿主函数,这样我的 Python 库可以有选择地暴露函数,然后由在 MicroPython 中运行的代码调用。
Codex 最终用 78 行 C 代码(https://github.com/simonw/micropython-wasm/blob/0.1a1/micropython_wasm/usercmodule/host/hostmodule.c)解决了这个问题,这些代码编译后变成了我随包分发的 362KB WebAssembly blob(https://github.com/simonw/micropython-wasm/blob/0.1a1/micropython_wasm/artifacts/micropython-wasi.wasm)。
我绝不是 C 程序员,但我阅读了 C 代码,并让两个不同的模型向我解释(这里是 Claude 的解释(https://claude.ai/share/62f74371-cc3c-44f2-b406-33d03513de9e)),而且我对其进行了大量测试。
与 WebAssembly 合作的好处是,如果 C 代码有致命缺陷,最坏的情况是 WebAssembly 执行失败并抛出异常。我可以忍受这种风险。
内存限制直接由 wasmtime 支持。CPU 限制稍微难一些:wasmtime 提供了“fuel(燃料)”的概念来限制 WebAssembly 调用可以执行的操作数量,这与此问题非常匹配,但其单位难以推理。我现在正在试验默认 2000 万的“燃料”设置,但我不确定它是否是最合适的值。
#### 自己试试
`micropython-wasm` alpha 现已上线 PyPI(https://pypi.org/project/micropython-wasm)。
你可以按照 README(https://github.com/simonw/micropython-wasm)中的描述,在自己的 Python 代码中尝试它。我还在 0.1a2 版本(https://github.com/simonw/micropython-wasm/releases/tag/0.1a2)中添加了一个简单的 CLI 模式,这意味着你可以使用 `uvx` 无需事先安装即可尝试,如下所示:
```bash
uvx micropython-wasm -c 'print("Hello world")'
# 观察它耗尽燃料:
uvx micropython-wasm -c 's = ""; while True: s += "longer"'
# 输出:micropython-wasm: guest exited with code 1
```
你也可以在 Datasette Agent(https://agent.datasette.io/)中这样尝试:
```bash
uvx llm keys set openai
# 粘贴你的 OpenAI 密钥,然后:
uvx --with datasette-agent \
--with datasette-agent-micropython \
--prerelease allow \
datasette --internal internal.db \
-s plugins.datasette-llm.default_model gpt-5.5 \
--root -o
```
然后导航到 http://127.0.0.1:8001/-/agent 并运行提示:
> `show me some micropython`
![一个聊天界面的截图,深蓝灰色头部左侧显示“home”,右侧显示“root”和一个汉堡菜单图标。下方是导航行,左侧有“← Back”和“Chat”,右侧有“EXPORT”按钮。一个蓝色用户消息气泡显示“show me some micropython”。下面是一个折叠的思考部分,显示“▸Thinking: ... to show the result clearly. After that, I can wrap up with a brief explanation!”,然后是一个“▶ Tool: execute_micropython”标签。接着是一个代码块:“# A tiny MicroPython example: blink-style logic + Fibonacci” / “def fib(n):” / “ a, b = 0, 1” / “ out = []” / “ for _ in range(n):” / “ out.append(a)” / “ a, b = b, a + b” / “ return out” / ‘print(“Hello from MicroPython!”)’ / ‘print(“First 10 Fibonacci numbers:”, fib(10))’ / “# MicroPython often runs on microcontrollers, e.g.:” / “# from machine import Pin” / “# led = Pin(2, Pin.OUT)” / “# led.value(1) # turn LED on” / “# led.value(0) # turn LED off”。水平分隔线下面是输出:“Hello from MicroPython!” / “First 10 Fibonacci numbers: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]”,然后是一个“▶ Result: execute_micropython”标签。底部是一个文本输入框,占位符为“Type a message...”,还有一个蓝色“Send”按钮。](https://static.simonwillison.net/static/2026/micropython-in-datasette-agent.jpg)
你可以通过使用 GitHub 账户登录 agent.datasette.io(https://agent.datasette.io/)来尝试该插件的实时演示。
#### 你应该信任我的“氛围编码”沙箱吗?
我刚刚抱怨了不成熟、维护松散的沙箱库,现在自己却构建了一个,这真是极具讽刺意味!
我故意给它打上了 alpha 版本标签,并且不准备向任何不愿承担重大风险的人推荐它。
我经过了充分的测试,以至于我自己用起来没问题。我已经发布了第一个使用它的插件,datasette-agent-micropython(https://github.com/datasette/datasette-agent-micropython)。我还将 GPT-5.5 xhigh 锁定在该 Datasette Agent 插件中,并挑战它打破沙箱(https://gist.github.com/simonw/5de497c44d25f9fd459c8aa2c959fe4a),到目前为止它还没成功。
我希望这个实现能说服一些拥有专业安全团队和高压问题的公司,让他们承诺使用 WebAssembly 中的 Python 作为沙箱方法,并开源他们自己的解决方案。
相似文章
micropython-wasm 0.1a2
MicroPython 移植到 WebAssembly,作为在浏览器中沙箱化执行 Python 的工具。
micropython-wasm 0.1a0
micropython-wasm 0.1a0 发布,使MicroPython能够在WebAssembly环境中运行,实现沙盒化和可移植性。
micropython-wasm 0.1a1
micropython-wasm 0.1a1 是一个 alpha 版本,它将 MicroPython 移植到 WebAssembly,从而能够在 Web 浏览器中执行 Python,并具备沙箱能力。
Edge Python:一个170KB的沙盒化Python子集(WASM),用于在客户端(直接在浏览器中)运行代理生成的代码
Edge Python 是一个基于 WASM 的 170 KB 沙盒化 Python 子集,无需服务器即可直接在浏览器中运行代理生成的代码,支持类、async/await、模式匹配等特性。
datasette-agent-micropython 0.1a0
datasette-agent-micropython 0.1a0 是一个早期 alpha 版本,它将 Micropython 集成到 Datasette 中,利用沙盒机制和 WebAssembly 实现安全执行。