Repository navigation
Releases: crmne/mxc
Release list
v0.1.0
MXC for Ruby 0.1.0 is the first release: run untrusted commands from Ruby in an operating system sandbox that decides what they can read, write, and reach, and how long they get.
gem install mxcPrecompiled gems are available for Linux (x86_64 and ARM64, glibc and musl), macOS (Intel and Apple Silicon), and Windows (x64 and ARM64), for Ruby 3.2 through 4.0.
New
Run a command in a sandbox with one call
result = MXC.run("python3 build.py", readable: "app/skills", writable: "tmp/out", timeout: 30)
result.success? # => true
result.stdout # => "Built report.pdf\n"MXC::Result also has stderr, exit_code, and timed_out?. By @crmne
Pass untrusted input as separate arguments
One String runs through the shell, like Kernel#system. A program and its arguments start directly, so each argument reaches the program unchanged:
MXC.run("python3", "convert.py", params[:filename], readable: "app/skills")By @crmne
Describe policies the Ruby way
Declare a sandbox with class macros:
class SkillSandbox < MXC::Sandbox
readable "app/skills"
writable "tmp/skills"
denied "app/skills/secrets"
directory "app/skills"
network allow: ["pypi.org"]
timeout 30
end
SkillSandbox.new.run("python3", "build.py")Or chain setters on MXC.sandbox: with_readable, with_writable, with_denied, with_network, with_env, with_directory, with_timeout, and with_backend. Paths take Strings or Pathnames, and timeouts take anything with to_f, including ActiveSupport::Duration. By @crmne
No network by default, or only the hosts you list
sandbox = MXC.sandbox.with_network(allow: ["pypi.org", "*.pythonhosted.org"])
result = sandbox.run("pip index versions flask")
result.blocked # => ["telemetry.example.com:443"]The command reaches the listed hosts over HTTP and HTTPS and nothing else. By @crmne
Stream output and drive interactive processes
SkillSandbox.new.run("python3", "build.py") { |chunk| print chunk.content }
process = SkillSandbox.new.spawn("python3", "-i", "-q")
process.write("print(6 * 7)\n")
process.close_write
process.each { |chunk| print chunk.content }
process.wait.success? # => trueA process can also be stopped with kill. By @crmne
Know whether the machine can sandbox before you rely on it
MXC.supported? # => true
SkillSandbox.new.supported? # => false
SkillSandbox.new.availability.remediation
# => "Install slirp4netns, iptables and util-linux ..."supported? starts a real sandbox once. MXC.backends lists what MXC found, and MXC.supports?(:network_policy) answers capability questions. By @crmne
Sandbox on Linux, macOS, and Windows
:auto picks Bubblewrap on Linux, Seatbelt on macOS, and Windows' own process containers (BaseContainer or AppContainer) on Windows. On Windows, one String runs through cmd.exe, and a program with separate arguments starts directly with each argument quoted. By @crmne
Run in Docker and Kamal
A guide with a minimal seccomp profile, the trade-offs of each /proc setup, a Bubblewrap wrapper that stops sandboxed code from creating user namespaces, and a Kamal job role configuration. By @crmne
Known limitations
- No host allow-lists on Windows. MXC can only confine a Windows sandbox to a proxy by also accepting inbound connections to it, so
with_network(allow: [...])raisesMXC::UnsupportedErrorthere.network: falseandnetwork: truework. - Windows programs that need the UI subsystem cannot start in a sandbox, such as PowerShell and
tasklist. Command-line tools such as Python and thecmd.exebuilt-ins work. - Windows builds without BaseContainer cannot hide
deniedpaths. MXC's AppContainer fallback does not enforce them, so a sandbox with denied paths raisesMXC::UnsupportedErrorthere. This includes Windows Server, Windows 11 24H2, and the 25H2 builds that lack BaseContainer. - On Windows builds without BaseContainer, granted paths get temporary access control entries. MXC removes them when the command finishes, but a crash of your process during a run leaves them in place.
- macOS gives sandboxed commands no writable
/tmp. Give the command a writable directory and pointTMPDIRat it. - Host allow-lists only cover programs that honor
HTTP_PROXYandHTTPS_PROXY. Everything else, including raw sockets, UDP, and DNS, fails inside the sandbox. See Policies for what allow-lists do and do not protect against. - Network access on Linux needs extra packages:
slirp4netns,iptables, util-linux, and/dev/net/tun. Sandboxes without network access need only Bubblewrap. - Docker needs a relaxed seccomp profile. Running in Docker explains exactly what it opens.
Thanks
Thanks to Microsoft's MXC team for building and open-sourcing MXC, and for the mxc-sdk crate this gem is built on. This gem is an unofficial binding and is not affiliated with or endorsed by Microsoft.
Read the documentation at crmne.github.io/mxc.
Full changelog: https://github.com/crmne/mxc/commits/v0.1.0