WAGI: WebAssembly Gateway Interface
github.com
github.com
I have been working on something like this for a while too. But it uses GraalVM and it's "Polyglot" runtime to provide support for JS/Ruby/Python/WASM/LLVM languages.
It's packaged as a single binary using graal-native + Quarkus, or if you don't care about that (faster startup time/lower memory use/no JVM needed) but need maximum performance, you can run it as a regular JVM service as well.
It was born out of a need for a polyglot Functions-as-a-Service platform but without the weight of container orchestration -- I needed something I could deploy as a single binary.
Essentially OpenFaaS/OpenWhisk, minus containers.
It doesn't use CGI as a request/response specification though, it uses Vert.x "RoutingContext" interface which has methods like ".getBodyAsJson()", ".params('something')", ".header('foo')", etc.
https://vertx.io/docs/apidocs/io/vertx/ext/web/RoutingContext.html
An open standard like CGI probably would have been better, but Graal has marshalling facilities for sharing object types between languages, so the most user-friendly/ergonomic thing to do was to share the underlying web request object itself across language boundaries, including all the methods you can call on it.---
This has given me motivation to finally finish it so I can publish + open-source it!
And what of all these languages that compile to JS, like Typescript, Reason, Kotlin, et al? Is JS a sound "IR" between the frontend and WebAssembly? Or would it be better to compile things directly to WebAssembly?
Like any bytecode (including jvm's), it comes at a performance penalty over bare asm. I personally don't anticipate it will replicate jvm's success as being a runtime when there is no reason for a runtime to exist - since it shouldn't be much harder to compile wasm (and languages that compile to wasm) to native asm ahead of time. On the other hand I wouldn't have predicted jvm's success either.
In places where you want a runtime though, like in shared computation models where it gives you security/performance advantages, it makes perfect sense. It looks like it's probably going to succeed there to me.
Compiling X -> JS -> Wasm doesn't make much sense. Not only is wasm a language with cleaner semantics to compile to, but JS doesn't actually really fit wasm semantics particularly well. In fact, while I could have missed something, I'm not aware of any good JS->wasm compilers. The closest I can think of is https://www.assemblyscript.org/ - and it's not 100% JS compatible.
There have been plenty of similar stacks, JVM is just one among many.
Imagine being able to run a complex C dependency (with a high risk of buffer overflows) risk-free because it's running in a WASM sandbox?
Or building features that allow users to provide their own executable code (for plugins and extensions and other customization features) where they code is executed in a sandbox with strictly controlled CPU/memory/network and API access control.
And only if that would be the case the use of WASM would be an advantage. For general extensibility we had Java servlets, Lua modules, PHP modules, etc in webservers for decades.
Maybe it’s interesting for source code obfuscation purposes or in order to allow migration of server logic between different languages. But even that was possible before - and I don’t think the Market need was huge.
Java servlets, Lua and PHP modules cannot be run securely, and there are vulnerabilities for all of them.
The thing is, the JVM was designed with the explicit intention of making it possible to run untrusted code safely: how do we know we got it right this time? The problem really isn't the VM, it's the APIs and authorization model for access to the underlying system's features.
Also because the API surface area that server side apps need is really just not big, JVM had a lot of problems because it tried to do too much.
That doesn't help if you need to run other people's code.
If all you think about is whether things are safe versus unsafe, everything is “fake security” and you’ll never come up with defense in depth.
The sandbox should make buffer overflows someone else’s problem, or at least an acceptable risk. You seem to be denying one of the most important reasons we have them?
A consequence, though, if the code is genuinely untrusted, is that you have to revalidate any requests after they’re sent to the system outside the sandbox. (Just as we can’t trust client-side validation on the server.) It seems like that would be duplicate work.
(An exception might be when an authenticated user is modifying their own files and you don’t care what they do.)
Yeah, I wonder how it would look like. /s
-it's extremely secure, tested for many years, etc...
-it's different than other VMs, almost no types or functions, the VM cannot access outside memory, only call functions supplied by the host (or write to stdout, again supplied by the host.)
-It loads fast, runs fast. Browser JavaScript has been very optimized.
-unrelated to the browser, but many languages can be compiled to WASM
What you see in this project is the WASI interface that provides the normal libc functions like fopen(), allowing WASM to act like a normal binary. But you can run WASM with other sets of functions, or none and the host can do all the moving of bytes.
Cloudflare, Netlify and others are trying to figure out how to host WASM for cloud functions because it's perfect for their systems: small, fast and safe.
Wasm on the server using WASI (which nowadays is probably either wasmtime or wasmer) doesn't have anything to do with Javascript.
Actually, wasm on the browser doesn't have anything to do with javascript either! This is actually unfortunate, because wasm in the browser can't natively interface with JS's GC yet [0].
On the other hand, I think a great strength of wasm is that wasm isn't tied to a single memory management system: if it forced a GC on you, it would also need to dictate the memory layout, and would need to determine how this GC gets run, all this would add unnecessary overheard and inflexibility when compiling low-level languages. Or when compiling any language that don't quite conform to the memory management model, different languages want to manage memory differently. This is a pain in the JVM and wasm fixed that.
Another strength is vendor neutrality. All major browsers embraced wasm, and it's got multiple implementations in the server as well. There isn't really a dominant implementation that others must conform, and wasm tools are interoperable between implementations. There are also embedded implementations (I know of wamr and wasm3).
On a darker note, I'm sad to see see one WASI runtime bashing another [1]; I wish the competition was more cordial, or at least, more professional.
[0] see the issues in this repo https://github.com/WebAssembly/gc
[1] https://wasmer.io/wasmer-vs-wasmtime - makes outlandish claims and uses benchmarks without clearly specifying what is being benchmarked (ideally it would link to a Github repo)
With something like CPython or the JVM, you’re attempting to sandbox something that was designed to be able to run arbitrary code, and was never designed to be restrained in this way, and if you miss even one thing, it’s completely broken. https://web.mit.edu/jesstess/www/pytennessee_sandbox.pdf is a good summary of what a problem this is, and why it’s basically impossible to secure that way: you’re plugging a huge number of leaks in a massive surface area, and you will not get it watertight—to say nothing of the fact that lots of software depends on those leaks existing (for perfectly benign reasons). And every C extension that you load increases the vulnerable surface area (e.g. https://www.hebergementwebs.com/news/escape-a-python-sandbox... describes using a numpy bug to escape a CPython sandbox). You just can’t run a successful sandbox inside CPython or the JVM; instead, you must run the language inside a sandbox—and even that is often troublesome.
WebAssembly, on the other hand, is isolated. There are no leaks, and the surface area is tiny, because it was designed that way. It can’t do anything directly save computation on what it’s given; it depends on the runtime to grant it access to any external resources. This completely guards against all direct attacks, leaving only side-channel attacks, which are a vastly less dangerous class in general, and easier to guard against once you know about them, because the runtime controls execution.
WASM has some interesting properties, like sandboxing, a small set of instructions you can fit in your head and some fairly high level constructs (for a compilation target).
However I don't quite know what other benefits it provides. It certainly cannot compete with the JVM/CLR on many fronts. And I don't see quite a lot of use-cases yet that cannot be covered with just integrating V8. Happy to learn more about that (actual rationales).
Yes, see: https://bytecodealliance.org/articles/1-year-update
> Or would it be better to compile things directly to WebAssembly?
It'd be even better to roll out one's own Lambda@Edge: https://krustlet.dev/
2 - it's the only things that also run on the web
3 - it's probably going to be ubiquitous because of that, meaning it will be a great platform to target by default
4 - it could potentially mean any platform run any code from any other one without having to deal with wrappers: you would write a lib in go or rust, and then run it as is in python
Sandboxing is of little help when external events can cause internal memory corruption that change module's expected outcomes.
It is as good as typical OS process.
A runtime like wasmer-python [0] is only 1.5MB.
Source: https://twitter.com/solomonstre/status/1111004913222324225?l...
[1]: https://github.com/WebAssembly/interface-types/blob/master/p...
It's obviously not a great design, and a more serious standardized version would use interface types. But that still seems quite a ways off.
"It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma."
So, if the implementation folds the multiple headers into one with the various values as a comma separated list, it should all be compliant. I don't know rust well enough to tell if this one is compliant...it reads like it is not (looks like last header wins): https://github.com/deislabs/wagi/blob/main/src/http_util.rs#...
Edit: It seems RFC7230 keeps most of that language, but makes one exception for Set-Cookie, but that's outbound in this context so it doesn't apply.
As for the fork() per request, I believe mostly because they were starting with requirements where avoiding fork() wasn't important. Conserving memory was probably more important, given the timeframe. CGI programs that leaked memory would have been common if they were daemons. For most web servers, there also probably weren't that many requests.
It was also a common existing pattern. Many of the services on unix boxes worked the same way...one daemon (inetd) would start instances of daemons per request and they would serve only one request.
That said, NSAPI appeared pretty quickly after CGI, then FastCGI, etc. Still pushing environment variables for headers, but stopping the request->fork->exit pattern and allowing persistent daemons.
Wasm is a high-level assembly language that has both a textual and binary format. It can be interpreted or compiled to a native representation, and has tight integration with modern JS engines (though JS is not required to run Wasm).
WASI (WebAssembly System Interface) is a runtime environment standard for Wasm that allows Wasm applications to integrate with 'standard' Unix APIs. These can be forwarded to the host system or emulated.
For more info, honestly just going to the Wasm site, the standard, or one of the runtime implementations and exploring from there is the way to go.
It's best to learn by doing, so if you know a language that can compile to Wasm, it's always a fun project to try to write something using it. Rust, D, C, Zig, and AssemblyScript all have pretty good tooling.
Hope this helps!
This is why we have been experimenting with the outbound HTTP implementation [1], which is available in WAGI. In theory, you could take the same approach as here, with the difference that a WebSockets connection is persistent and long-running.
that said, according to the docs this seems to be cgi 1.1 which equates to a process per request, which afaik isnt so scalable.
> Wagi is an implementation of CGI for WebAssembly.