Show HN: Sandboxing untrusted code using WebAssembly
65 points
8 hours ago
| 6 comments
| github.com
| HN
Hi everyone,

I built a runtime to isolate untrusted code using wasm sandboxes.

Basically, it protects your host system from problems that untrusted code can cause. We’ve had a great discussion about sandboxing in Python lately that elaborates a bit more on the problem [1]. In TypeScript, wasm integration is even more natural thanks to the close proximity between both ecosystems.

The core is built in Rust. On top of that, I use WASI 0.2 via wasmtime and the component model, along with custom SDKs that keep things as idiomatic as possible.

For example, in Python we have a simple decorator:

  from capsule import task

  @task(
      name="analyze_data", 
      compute="MEDIUM",
      ram="512mb",
      allowed_files=["./authorized-folder/"],
      timeout="30s", 
      max_retries=1
  )
  def analyze_data(dataset: list) -> dict:
      """Process data in an isolated, resource-controlled environment."""
      # Your code runs safely in a Wasm sandbox
      return {"processed": len(dataset), "status": "complete"}
And in TypeScript we have a wrapper:

  import { task } from "@capsule-run/sdk"

  export const analyze = task({
      name: "analyzeData", 
      compute: "MEDIUM", 
      ram: "512mb",
      allowedFiles: ["./authorized-folder/"],
      timeout: 30000, 
      maxRetries: 1
  }, (dataset: number[]) => {
      return {processed: dataset.length, status: "complete"}
  });
You can set CPU (with compute), memory, filesystem access, and retries to keep precise control over your tasks.

It's still quite early, but I'd love feedback. I’ll be around to answer questions.

GitHub: https://github.com/mavdol/capsule

[1] https://news.ycombinator.com/item?id=46500510

Asmod4n
2 hours ago
[-]
Mruby has something like that build in, you can create a VM which only has basic data types and control flow, no i/o, rng, time, meta programming or any host access possible simply because most functionality is only available as gems and they simply aren’t loaded. Everything you can do with it should be fully deterministic.
reply
yohguy
6 hours ago
[-]
It looks really promising but I would love more examples as to how to actually use this with AI agents. Reading the homepage it is not clear if we are meant to have the Agent spun up and act fully in the sandbox (something like the HTTP example) or do we take the result code message from an AI agent and then run it dynamically (with eval?).

That being said this is useful even if it wasn't for the running AI agent code aspect, being able to limit ram and cpu usage and time outs makes it easier to run coding based games/applications safely (like battle snakes and Leetcode)

reply
mavdol04
5 hours ago
[-]
Thanks! Got it, I will add more examples for that. Currently you can do both: run dynamically untrusted code with eval, or run fully encapsulated logic (like in the existing examples).

I made a small example that might give you a better idea (it's not eval, but shows how to isolate a specific data processing task): https://github.com/mavdol/capsule/tree/main/examples/javascr...

And yes, you are spot on regarding LeetCode platforms. The resource limits are also designed for that kind of usage.

reply
fix4fun
6 minutes ago
[-]
yep, more examples with agents use will be helpful
reply
zachrip
5 hours ago
[-]
Would like to see the eval version - the dialogue version just seems like normal code with extra steps?
reply
mavdol04
4 hours ago
[-]
yeah, the previous example was quite basic. I will write a complete example for that, but here is how you can run dynamic code:

   import { task } from "@capsule-run/sdk";

   export default task({
     name: "main",
     compute: "HIGH",
   }, async () => {
     const untrustedCode = "const x = 10; x * 2 + 5;";
     const result = eval(untrustedCode);
     return result;
   });
Hope that helps!
reply
bigblind
4 hours ago
[-]
This looks very neat indeed! Are there any plans to adding network limits? Like, you might want to avoid an agent running code that just requests a resource in a loop, or downloads massive amounts of data.
reply
mavdol04
4 hours ago
[-]
Thanks! Not yet, but that's a great idea. I could definitely add it to the roadmap.
reply
gregpr07
6 hours ago
[-]
Why go this route? Why Python is more powerful than JS is mostly because of third party plugins like pandas which are excplicitly not supported (C bindings, is this possible to fix?)...

At that point it might be just easier to convince the model to write JS directly

reply
simonw
6 hours ago
[-]
You can run libraries like Pandas in WebAssembly in Pyodide - in fact Pandas works already. Here's a demo I built with it a while ago: https://tools.simonwillison.net/pyodide-bar-chart

It's not too hard to compile a C extension for Python to a WebAssembly and bundle that in a .so file in a wheel. I did an experiment with that the other day: https://github.com/simonw/tiny-haversine?tab=readme-ov-file#...

reply
mavdol04
5 hours ago
[-]
I would love for the component model tooling to reach that level of maturity.

Since the runtime uses standard WASI and not Emscripten, we don't have that seamless dynamic linking yet. It will be interesting to see how the WASI path eventually converges with what Pyodide can do today regarding C-extensions.

reply
mavdol04
6 hours ago
[-]
I understand your point. I added native Python support because C extensions will eventually become compatible. Also, we might see more libraries built with Rust extensions appearing, which will be much easier to port to Wasm.
reply
koolala
6 hours ago
[-]
It seems import to highlight these more. Aren't all the limitations of using this based around their limitations?

componentize-py – Python to WebAssembly Component compilation

+

jco – JavaScript toolchain for WebAssembly Components

I'm curious how Wasi 0.3 cross language components will go for something like this.

reply
avaer
6 hours ago
[-]
I agree; this project looks impressive, but I'm guessing there are some rough edges in the transpilation "magic" that should be called out.

That's the crux of how usable this is going to be for people's use cases, and it's better to document the limitations upfront.

reply
mavdol04
6 hours ago
[-]
I recreated many Node.js built-ins so compatibility is actually quite extended.

For Python, the main limitation is indeed C extensions. I'm looking for solutions. the move to WASI 0.3 will certainly help with that.

reply
simonw
5 hours ago
[-]
The decorator syntax is neat but confusing to me - I would need to understand exactly what it's doing in order to trust it.

I'd find this a lot easier to trust it if had the Python code that runs in WASM as an entirely separate Python file, then it would be very clear to me which bits of code run in WASM.

reply
smithclay
5 hours ago
[-]
Personally: love the decorator pattern after I got used to it :)

Posted this yesterday as well, but seems like a really nice emerging pythonic way to call out to remote infrastructure (see: Modal[1]).

[1]: https://modal.com/docs/examples/hackernews_alerts#defining-t...

reply
mavdol04
5 hours ago
[-]
Thanks for the feedback! What do you think about running the separate file directly from the decorator?
reply
simonw
3 hours ago
[-]
I'd love that. I want to be able to look at the system and 100% understand which code is running directly and which code is running inside the sandbox.
reply