Hi all! I'm somewhat new to wasm and wasmtime. I'm currently exploring information flow control for wasm plugins. The idea is to label data when a plugin reads it (e.g. a file, an HTTP response) and block it from reaching sinks it shouldn't (the network, stdout, other files).
To see every source and sink, I've been building a middleware layer for wasmtime hosts. It's basically a chain of before/after hooks that run around every call crossing the host boundary (including the host's own imports, the component's exports and WASI p2/p3). Hooks can deny calls, and they also see the resource handles each call takes/returns, which lets a label follow data from, say, a file descriptor to the stream read from it.
Since wasmtime has no hook for intercepting calls AFAICT, and a Linker can't hand back a definition to wrap, I register my own implementation of each interface instead, which runs the chain and then forwards to the original. For p3 streams, whose data moves outside of calls, the host swaps in its own stream and runs the chain on each chunk.
Has anyone done something similar? Is this a terrible idea, or is there a better approach? I looked at composition (splicer, WASI-Virt), but if I understand correctly those can't reach host-provided imports, which is where most sources and sinks live.
I'd recommend looking into using component composition for this kind of thing
Hi Luiz!
I'm doing almost the exact same thing: Components are dynamically loaded into a host that uses Wasmtime. Every wasi: interface provided is a proxy to control and/or deny access, also for IFC (though my current system is very coarse-grained and probably not technically IFC yet). Users or workflow definitions (pre-configured graphs of components) can enable access to sensitive sinks with a permissions system.
I'm curious in both the interface proxy implementations, and what you're using components and IFC for (I have a guess). Maybe we could connect off-forum on the IFC bits.
@Justin Fagnani exciting to hear of others working on similar things! :slight_smile:
At a high level I'm looking for ways to build applications that allow third-party plugins, where plugins need explicit user consent to access sensitive resources, and where data flows between the host and its plugins (and maybe between plugins) are tracked. Does that sound similar to your use case?
So far I've tried three approaches for the layer that intercepts calls between plugins and the host:
send_request hook, and gate my own host imports with the user's grants. It's simple, but each check is a one-off decision, so it can't follow data after a call. This is how my application works today, but it's not enough for IFC.add_to_linker implementations that run a chain of before/after hooks and then forward to wasmtime-wasi's. It sees every WASI call, its resource handles, and p3 stream chunks. It needs a wrapper per WASI function, and these are coupled to the traits wasmtime-wasi generates with bindgen!, which can change between Wasmtime releases even when WASI doesn't.I also looked into splicer, but AFAICT it only inserts middleware on edges between components inside a composition, so it can't reach host imports.
I'm leaning towards composition now, but haven't committed yet. Are your proxies hand-written per interface? And yes, I'd love to connect off-forum!
@fitzgen (he/him) thanks for the pointer! I prototyped the composition approach and it worked well on Wasmtime. Is there a reason beyond portability to prefer it over wrapping wasmtime-wasi's implementations on the host? E.g. are its generated traits not meant to be used this way?
One component-model question I ran into, if anyone knows: to observe when a plugin closes a p3 stream (e.g. the one passed to write-via-stream), the wrapper has to relay its bytes after the sync call returns. I ended up with a second, long-running export that the host drives alongside the plugin's. Is there a more idiomatic way? (jco 1.35 doesn't seem to run two exports concurrently, so this part didn't work there)
Thanks again for all your responses and help here!
Hi @Luiz Ribeiro would you mind sharing a bit more about the failure you saw with Jco? A reproducer would be really helpful or even just a more in depth explanation for what wasn't working would be great!
Luiz Ribeiro said:
fitzgen (he/him) thanks for the pointer! I prototyped the composition approach and it worked well on Wasmtime. Is there a reason beyond portability to prefer it over wrapping
wasmtime-wasi's implementations on the host? E.g. are its generated traits not meant to be used this way?
With composition, you also put your code inside the sandbox, so it doesn't need to be part of the TCB, but if wrapping traits is working well for you then by all means
Victor Adossi said:
Hi Luiz Ribeiro would you mind sharing a bit more about the failure you saw with Jco? A reproducer would be really helpful or even just a more in depth explanation for what wasn't working would be great!
@Victor Adossi thanks for following up! The concurrent exports thing turned out to be the wrong root cause. The actual problem was that a resource returned by an async host import ends up in the wrong handle table when the component is part of a composition. I've filed an issue with a repro here: https://github.com/bytecodealliance/jco/issues/2182, and opened a PR with a fix: https://github.com/bytecodealliance/jco/pull/2183
Fair warning that I leaned heavily on AI for the investigation and fix as I'm not familiar with jco's internals, so it could use a careful look.
Thanks @Luiz Ribeiro -- I'll take a look at the issue and the fix! Some of this might be fixed by a recently released version of componentize-js but I appreciate the contribution :)
Last updated: Oct 11 2026 at 02:20 UTC