Wasval
Wasval is a Ruby sandbox that executes Ruby code safely inside a WebAssembly runtime (via wasmtime).
Installation
bundle add wasval
Or install directly:
gem install wasval
Setup
Wasval requires a ruby.wasm binary. Install it with the built-in installer:
require "wasval"
Wasval::Install::RubyWasm.new.install
install downloads the ruby.wasm binary and also generates a pre-compiled ruby.cwasm (serialized WebAssembly module) alongside it. Both files are saved to ~/.wasval/ by default (ruby.wasm and ruby.cwasm). The .cwasm file is a pre-compiled module that significantly reduces startup time on subsequent executions, though it is larger in file size than the .wasm binary.
If you only need the .wasm binary without pre-compilation, use download instead:
Wasval::Install::RubyWasm.new.download
The binary is downloaded from the ruby.wasm releases.
By default, Ruby 3.4 is used. You can change the Ruby version via the WASVAL_RUBY_VERSION environment variable or the ruby_version: option:
Wasval::Install::RubyWasm.new(ruby_version: "3.3").install
WASVAL_RUBY_VERSION=3.3 bundle exec ruby -e "require 'wasval'; Wasval::Install::RubyWasm.new.install"
You can customize the destination via the WASVAL_RUBY_WASM_PATH environment variable or the dest: option:
Wasval::Install::RubyWasm.new(dest: "/path/to/ruby.wasm").download
Bundling gems into the binary (include_gems)
By default, the installed ruby.wasm binary does not include the standard library or gems, so require calls inside the sandbox will fail. To bundle the standard library (and any gems installed in the usr directory of the tarball) into the binary, use the include_gems: true option:
Wasval::Install::RubyWasm.new(
include_gems: true,
pack_output: "/path/to/ruby-packed.wasm",
serialized_dest: "/path/to/ruby.cwasm"
).install
This extracts the usr directory from the downloaded tarball, packs it into the binary via rbwasm pack, and serializes the result. The packed binary is written to pack_output (defaults to ~/.wasval/ruby-packed.wasm), and the serialized module is written to serialized_dest.
If you have a pre-existing usr directory (e.g. with additional gems installed), pass its path via include_gems::
Wasval::Install::RubyWasm.new(
include_gems: true,
include_gems: "/path/to/usr"
).install
After installing with include_gems: true, point WASVAL_RUBY_CWASM_PATH to the serialized binary and require will work inside the sandbox:
result = Wasval.execute("require 'json'; JSON.parse('1')")
result.status # => :success
Usage
Set the WASVAL_RUBY_WASM_PATH environment variable to point to the ruby.wasm binary, then execute Ruby code:
result = Wasval.execute("puts 'hello'")
result.status # => :success
result.output # => "hello\n"
result.success? # => true
Configuration
Wasval.configure do |config|
config.timeout = 10 # seconds (default: 5)
config.memory_limit = 256 # MB (default: 128)
end
You can also pass per-call overrides:
result = Wasval.execute(code, timeout: 3, memory_limit: 32)
Result
Wasval.execute returns a Wasval::Result with the following attributes:
| Attribute | Description |
|---|---|
status |
:success, :syntax_error, :runtime_error, :timeout, :memory_limit, :sandbox_error |
output |
Captured stdout |
stderr |
Captured stderr |
error_message |
Human-readable error description (nil on success) |
Helper methods: success?, timeout?, error_type.
Performance and Memory Considerations
Wasval supports two binary formats for the Ruby WebAssembly runtime:
.wasm— The standard WebAssembly binary. It is portable and can be used across different WebAssembly runtimes, but requires compilation at load time, which results in higher memory usage and longer startup latency on first use..cwasm— A serialized (pre-compiled) module specific to Wasmtime. It is the output of ahead-of-time compilation of the.wasmbinary, so the runtime can load it directly without recompilation. This eliminates the compilation overhead, significantly reducing both memory usage and first execution latency. Note that.cwasmfiles are larger than their.wasmcounterparts and are tied to the specific version of Wasmtime used to generate them.
When loading a .wasm binary at runtime, please be aware of the following:
- Memory usage: Loading ruby.wasm requires a significant amount of memory. Ensure your environment has sufficient memory available before using this gem.
- First execution latency: The first call to
Wasval.executeincurs additional overhead due to loading and initializing the WebAssembly runtime. Subsequent calls will be faster.
Using .cwasm (generated by Wasval::Install::RubyWasm.new.install) is recommended for production use, as both concerns above do not apply.
Development
After checking out the repo, run bin/setup to install dependencies. Then, install the ruby.wasm binary:
bundle exec ruby -e "require 'wasval'; Wasval::Install::RubyWasm.new.download"
Set WASVAL_RUBY_WASM_PATH and run the tests:
export WASVAL_RUBY_WASM_PATH=~/.wasval/ruby.wasm
bundle exec rake test
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/y-yagi/wasval.
License
The gem is available as open source under the terms of the MIT License.