What sandboxing does and does not give you
A sandbox in mcqemu is a QEMU virtual machine with two defaults changed: its disk
is a copy-on-write overlay over a base image, and its outbound networking is
switched off. sandbox_vm does the overlay, the launch, and the wait for the
guest agent in one call; sandbox_destroy tears the whole thing down. That is
the entire mechanism. Everything below is about what those two defaults buy you
and where they stop.
A separate kernel is the real boundary
Section titled “A separate kernel is the real boundary”The isolation that matters here is not something mcqemu implements. It is the fact that a guest runs its own kernel on emulated hardware. Guest processes make syscalls into the guest kernel, which drives virtual devices, which QEMU implements in userspace on the host. Nothing in the guest addresses host memory, host processes, or host files, because there is no path from guest userspace to those things that does not go through the device model.
That is a much stronger position than a container, where the guest and the host share one kernel and isolation is a matter of namespaces and seccomp filters being configured correctly. If you are running software you have reason to distrust, the kernel boundary is the reason to reach for a VM.
The cost is that you pay for it. A VM boots a whole operating system, wants its own memory, and (when the guest architecture does not match the host) runs under TCG emulation, which is correct but slow.
The overlay: the base image is never written
Section titled “The overlay: the base image is never written”sandbox_vm creates a qcow2 overlay whose backing file is your base image, and
gives the VM only the overlay. Every write the guest makes lands in the overlay.
The base image is opened read-only through the backing chain and comes out
byte-identical no matter what happens inside.
This is what makes sandboxes cheap enough to be disposable. Creating one is
writing a small file with a pointer to a big one, so a fresh sandbox from a
20 GB base costs kilobytes and a second or two. It is also what makes
sandbox_destroy safe: it deletes the overlay and the VM’s state and runtime
directories, and reports the base image path back under base_image_untouched
so the answer is visible rather than assumed.
Two consequences worth knowing. The overlay is recreated fresh on every
sandbox_vm call for a given name, so a stale overlay from a dead sandbox never
resurrects old state. And because the base is genuinely in use while a sandbox
runs, mcqemu counts it among the disks in use, so an attempt to modify the base
underneath a running sandbox is refused.
If you want state to survive, take a snapshot (see Snapshots)
or launch a normal VM against a real disk with launch_vm instead. Sandboxes
are for work you intend to throw away. Disposable sandboxes
walks through the loop.
Outbound networking is off by default
Section titled “Outbound networking is off by default”sandbox_vm launches with QEMU’s user-mode networking (SLIRP) in restricted
mode. Guest-initiated traffic is dropped. Inbound port forwards keep working, so
the default forward from a free host port to guest port 22 is still how you get
in, and guest_exec over the agent socket is unaffected because it never touches
the network at all.
The reason this is the default is specific, and it is worth understanding rather
than taking on faith. SLIRP synthesizes a small private network for the guest,
and in that network the address 10.0.2.2 is the host’s loopback interface. A
guest with unrestricted user-mode networking can therefore open connections to
services listening on the host’s 127.0.0.1: a development database that never
bothered with authentication because it only listens locally, an SSH agent
forwarding socket, an unauthenticated Ollama or Redis, a docker API on a TCP
port. The mental model of “it is only on localhost, so it is private” is exactly
the model SLIRP breaks. restrict=on closes that path along with internet
access.
When a guest legitimately needs to fetch packages, pass allow_network=True, and
be aware you are re-opening the host loopback path at the same time. There is no
setting that gives internet access while blocking 10.0.2.2, because SLIRP does
not offer that distinction. If you need it, give the VM a real bridged or TAP
interface and enforce the policy in your host firewall, which means starting
QEMU yourself and registering it with
attach_vm.
launch_vm takes the same restrict_net flag, plus no_net=True to remove the
network card entirely. A VM with no NIC has no forwards either, so it is only
reachable through the guest agent and the display.
Where the boundary ends
Section titled “Where the boundary ends”Everything above is real. None of it makes this a hardened security boundary against a determined attacker.
QEMU escapes exist. The device model is a large body of C code parsing input that a hostile guest controls, and it has had exploitable bugs, some of them serious. mcqemu changes nothing about that surface. If your threat model includes an adversary willing to burn a QEMU escape on you, a mcqemu sandbox is not the control you want on its own. Real defense in depth for that case means running QEMU as a dedicated unprivileged user, on a host that holds nothing valuable, behind whatever seccomp and MAC policy your distribution offers, and not on your workstation.
The QEMU process runs as you. It has your file permissions, your network access, and your ability to write to your own home directory. The boundary being defended is “the guest cannot reach the host”, and it holds against the guest playing by the rules of the device model. It does not hold against an escape, because the thing that escapes lands with your privileges.
extra_args can weaken isolation. launch_vm accepts raw QEMU flags,
because there will always be a machine type or device you need and no wrapper
anticipated. That escape hatch is an operator tool. Values you wrote yourself are
fine; values derived from anything untrusted are not, because a few flags turn a
VM into a hole straight through to the host. Those are rejected outright:
| Rejected | Why |
|---|---|
-fsdev, -virtfs | Host filesystem passthrough into the guest |
-drive / -blockdev naming /dev/... | Attaches a host block device |
-chardev with spawn | Runs a host command |
-runas | Changes the user the QEMU process runs as |
-monitor | Exposes the human monitor outside the managed socket |
-qmp, -pidfile, -daemonize | Collide with the sockets and process model mcqemu manages |
Read that list as what it is: a guardrail against an agent constructing arguments
from untrusted input, not a security perimeter. It is a fixed list of known-bad
flags, not a proof that everything else is safe, and QEMU has a very large flag
surface. An operator who genuinely wants host passthrough can start QEMU by hand
and manage it with attach_vm, and nothing stops them, which is correct. The
point of the check is that the path to weakening isolation should require a
person deciding to do it.
The guest agent is root inside the guest, by design. guest_exec runs
commands as the agent’s user, which is normally root, and guest_file_write will
write anywhere the agent can reach. That is not a leak, it is the feature: you
already control the VM’s power button, its disks and its display from the host,
so declining to give you a shell would be theatre. What it does mean is that
installing the guest agent is a decision about the guest, not a neutral
convenience. Do not install it in a VM whose contents you are supposed to be
analysing at arm’s length, and remember that a sandbox with the agent running has
no meaningful defense against the host, only against the guest reaching out.
A summary you can act on
Section titled “A summary you can act on”Protected: your host filesystem from guest processes, your host services on loopback (with the default restricted networking), your base images from any guest writes, and your host kernel from guest syscalls.
Not protected: anything, if QEMU itself is exploited; anything, if you pass
isolation-weakening extra_args; the guest’s own contents from you, since the
agent and the monitor give you full control; and the host loopback path, if you
pass allow_network=True.
Use a sandbox to run software you have not read, to test an installer, to try a package that wants to touch your whole system, or to give an agent somewhere to make a mess. Do not use one as the only thing standing between you and code written specifically to attack you.