Download and install the latest release matching the host before the first run:
python3 scripts/nvx.py download
python3 scripts/nvx.py runThis installs the packaged kernel and initramfs under build/ and OpenVMM
under openvmm/target/release/, so no local build is required. Set GH_TOKEN
to a token with contents read access when downloading from a private repository.
On Linux, pass --hypervisor mshv to both commands to use the MSHV package.
The CLI chooses WHP on Windows and KVM on Linux:
python3 scripts/nvx.py runA successful Alpine boot prints both ALPINE-MICROVM-BOOT-OK and
NVX-GUEST-BOOT-OK: alpine. Boot Ubuntu userland with the same NVX kernel:
python3 scripts/nvx.py run --guest ubuntuUbuntu defaults to 512 MiB and prints NVX-GUEST-BOOT-OK: ubuntu. It is
Ubuntu userland with the NVX kernel, not a stock Ubuntu kernel or systemd VM.
Boot Azure Linux 3.0 userland the same way with --guest azurelinux. The
kernel unpacks its rootfs into a RAM filesystem capped at half of guest memory,
so it defaults to 512 MiB, and it prints NVX-GUEST-BOOT-OK: azurelinux.
Exit cleanly from the guest with:
/sbin/nvx-exit 0Pass extra guest options without changing the generated device ABI:
python3 scripts/nvx.py run \
--memory-mib 256 \
--net 10.0.0.2/24 \
--network-profile portable \
--cmdline "quiet loglevel=0"Networking requires the explicit portable capability profile. It uses the
same in-process data plane on KVM, MSHV, and WHP; omitting either --net or
--network-profile portable is rejected before OpenVMM starts.
Select an explicit processor count after building the matching specialized guest kernel:
python3 scripts/nvx.py run --machine microvm --processors 8The microVM uses fixed device topology, reserves a shared interrupt-status page, and uses shared-status edge-triggered virtio interrupts with 1, 2, 4, or 8 vCPUs.
The platform release archives are self-contained; scripts/nvx.py is a
convenience wrapper and is not required at runtime. Extract the archive that
matches the host and run the following command from its
nvx-VERSION-PLATFORM directory. For Linux/KVM:
./bin/openvmm \
--single-process \
--machine microvm \
--processors 1 \
--hypervisor kvm \
--memory 128M \
--kernel guest/vmlinux \
--initrd guest/initramfs.cpio.gzFor Windows/WHP in PowerShell:
.\bin\openvmm.exe `
--single-process `
--machine microvm `
--processors 1 `
--hypervisor whp `
--memory 128M `
--kernel guest\vmlinux `
--initrd guest\initramfs.cpio.gzFor Linux/MSHV, use the linux-mshv archive and replace kvm with mshv.
If the artifacts are already installed in the repository layout, use
openvmm/target/release/openvmm[.exe], build/vmlinux, and
build/initramfs.cpio.gz instead of the paths above.
For Ubuntu userland, use 512 MiB and select
guest/initramfs-ubuntu.cpio.gz or
build/initramfs-ubuntu.cpio.gz as the initrd. Below 320 MiB, the kernel
cannot unpack the whole Ubuntu initramfs and boots a truncated root. The
kernel path remains unchanged.
Direct OpenVMM launches accept generic directional network defaults:
./bin/openvmm \
--single-process \
--machine microvm \
--hypervisor kvm \
--memory 128M \
--kernel guest/vmlinux \
--initrd guest/initramfs.cpio.gz \
--net 10.0.0.2/24 \
--network-profile portable \
--network-egress allow \
--network-ingress denyEgress defaults to allow and ingress defaults to deny. With ingress denied,
responses to guest-initiated connections remain available, while new inbound
connections do not. The portable profile supports egress allow or deny but
rejects ingress allow before the workload starts.
For destination and port rules, select an explicit default and repeat generic allow/deny options:
./bin/openvmm \
--single-process \
--machine microvm \
--hypervisor kvm \
--memory 128M \
--kernel guest/vmlinux \
--initrd guest/initramfs.cpio.gz \
--net 10.0.0.2/24 \
--network-profile portable \
--network-egress deny \
--network-egress-allow 140.82.112.0/20:tcp:443 \
--network-egress-deny 140.82.114.0/24:tcp:443Rules match IPv4 addresses or CIDRs and may add one TCP or UDP destination port. Deny matches take precedence over allow matches.
NVX can lower inclusive TCP/UDP port ranges and rule-local IPv4 exclusions to those native rules:
{
"allow": [
{
"cidr": "192.0.2.0/24",
"except": ["192.0.2.128/25"],
"protocol": "tcp",
"port": 8000,
"endPort": 8010
},
{
"cidr": "192.0.2.200/32"
}
],
"deny": [
{
"cidr": "192.0.2.0/24",
"protocol": "tcp",
"port": 8005
}
]
}Pass the file with --network-egress-policy-file PATH on run, one-shot
sandbox run, or sandbox provision. An explicit --network-egress allow or
deny is required. The file option cannot be mixed with
--network-egress-allow or --network-egress-deny.
The root accepts only allow and deny arrays. Each rule requires one IPv4
cidr; optional except entries must be IPv4 CIDRs contained by that parent.
Host bits are normalized like the native CIDR syntax: 10.0.0.5/24 means
10.0.0.0/24, not one host. Use /32 to select one IPv4 address.
Duplicate JSON properties, unknown fields, and explicit null protocol values
are rejected. Policy files are limited to 1 MiB of UTF-8 input.
protocol is tcp or udp and requires port in 1..65535. Optional
endPort is inclusive, must be in 1..65535, and cannot be below port.
Omitting the protocol and ports matches every IPv4 transport supported by the
native rule. Protocol-wide TCP/UDP rules without a port and IPv6 are not
supported.
Exclusions affect only their containing rule: they never become global deny rules. A later allow rule may therefore match an address excluded from an earlier allow rule, while an address excluded from a deny rule falls through to other rules and the explicit default. Explicit deny matches still take precedence over allow matches.
NVX canonicalizes safely equivalent prefixes and rejects policies that lower to more than 256 allow rules or 256 deny rules. It rejects oversized expansions before launch rather than truncating or widening them. Managed provision stores the validated lowered rules in sandbox state, so later starts do not reread a mutable source policy file.
Host-loopback denial and deliberate localhost port publishing are separately controlled from ordinary egress:
./bin/openvmm \
--single-process \
--machine microvm \
--hypervisor kvm \
--memory 128M \
--kernel guest/vmlinux \
--initrd guest/initramfs.cpio.gz \
--net 10.0.0.2/24 \
--network-profile portable \
--host-loopback deny \
--network-proxy 10.0.0.1:3128With deny, general guest-to-host loopback and every host-to-guest forward are
blocked, while the exact TCP proxy endpoint remains available. UDP on that
same port and other host service ports remain blocked even with ordinary
egress allowed.
The portable profile does not support generic bidirectional host-loopback
allow. Explicit --host-loopback allow without any forward is rejected before
VM resources are opened. For deliberate port publishing, repeat
--host-loopback-forward tcp:HOST_PORT:GUEST_PORT or its UDP form to expose
only selected localhost ports toward the guest, with explicit
--host-loopback allow. These forwards do not satisfy a generic allow policy
that provides no port list. Omitting --host-loopback preserves existing
guest-to-host mapping without publishing guest ports. Guest-originated traffic
remains subject to egress policy.
Most nvx.py run options pass through unchanged: --machine, --processors,
--cpu-profile, --mount, --net, --network-profile, --cmdline,
--restore-snapshot, --restore-processors, and --restore-ready-path. The
wrapper performs these translations and additions:
nvx.py run |
Direct OpenVMM option |
|---|---|
--guest alpine |
--initrd .../initramfs.cpio.gz on a fresh boot |
--guest ubuntu |
--initrd .../initramfs-ubuntu.cpio.gz and a 512 MiB default on a fresh boot |
--guest azurelinux |
--initrd .../initramfs-azurelinux.cpio.gz and a 512 MiB default on a fresh boot |
--hypervisor auto |
--hypervisor kvm on Linux or --hypervisor whp on Windows |
--memory-mib N |
--memory NM |
--memory-capacity-mib N |
--memory-capacity NM on a fresh boot |
--restore-memory-mib N |
--restore-memory NM |
--dry-run |
No equivalent; this only prints the generated command |
Always include --single-process. When restoring, omit --memory, --kernel,
and --initrd. Every restore gives the guest fresh entropy through the time
ABI's restore packet, so --restore-entropy is no longer needed; OpenVMM still
accepts it without effect. Do not pass --guest ubuntu during restore; the
captured RAM and machine contract already identify the restored guest. For
example:
./bin/openvmm \
--single-process \
--machine microvm \
--processors 8 \
--hypervisor kvm \
--restore-snapshot /var/lib/nvx/snapshot \
--restore-processors 4 \
--restore-memory 1024M \
--restore-ready-path /run/nvx/restore-ready.sockmicrovm is now the only selector and launches the contract previously named
microvm-v2. The microvm-v2 spelling, the former one-vCPU ABI-1 behavior,
ABI-1 device-I/O control, TTRPC numeric value 1, ABI-1 snapshot restore, and
boot-layout-1 snapshot restore are removed. Snapshot metadata and performance
series continue to use numeric ABI value 2 and boot-layout value 2.
Use NVX commit cb52bcd454b454cb241096c33ed42a1dcdc65347 with OpenVMM commit
1b70365613517a10718e00284a62bdffbd80e41c, or an earlier compatible pair, to
run retired ABI-1 guests or snapshots.
Restore an existing microVM snapshot with an optional host-readiness endpoint:
python3 scripts/nvx.py run \
--machine microvm \
--processors 8 \
--restore-snapshot /var/lib/nvx/snapshot \
--restore-processors 4 \
--restore-memory-mib 1024 \
--restore-ready-path /run/nvx/restore-ready.sockThe endpoint must already be listening. OpenVMM connects to a Unix domain
socket on Linux or a //./pipe/... named pipe on Windows and writes exactly
OPENVMM_RESTORE_READY_V1\n after snapshot verification, attachment
resolution, worker startup, gated guest repair, and host-input re-enable
complete while the restored vCPU remains stopped. With
--restore-processors, the snapshot keeps its immutable eight-vCPU capacity;
the source must have captured a canonical maxcpus boot-online prefix, and the
guest onlines exactly the requested prefix before readiness and host input
release. Targets are limited to 1, 2, 4, or 8 and cannot be below the captured
boot-online count or above capacity. Legacy snapshots reject the option. The
peer must accept and read while startup is in progress; Windows flush
completion waits for the named-pipe peer to consume the frame. Failure to
write the complete event aborts and tears down the restore.
For restore-time memory expansion, capture a fresh snapshot with
--memory-mib 512 --memory-capacity-mib 2048, then select a target with
--restore-memory-mib 512, 1024, or 2048. The snapshot's memory.bin
remains exactly 512 MiB; selected expansion ranges receive fresh per-launch
backing and are onlined before restore readiness.
The microVM has two mapping slots with the fixed tags microvm and
microvm1. On a cold boot with --mount, the initramfs mounts each mapping
automatically:
python3 scripts/nvx.py run --mount "/mnt/host,/absolute/host/share,rw"PowerShell example:
python scripts\nvx.py run `
--mount "/mnt/host,C:\Users\me\microvm-share,rw"Use ro for read-only access. The guest target must be an absolute Linux path.
Host paths containing commas are unsupported. Repeat --mount once to attach
a second directory with its own target and access mode, for example a
read-write workspace next to a read-only tool cache:
python3 scripts/nvx.py run \
--mount "/workspace,/srv/checkout,rw" \
--mount "/opt/hostedtoolcache,/opt/hostedtoolcache,ro"The first mapping uses tag microvm and the second tag microvm1. OpenVMM
enforces each mapping's access mode on the host, so a read-only mapping
rejects writes with EROFS even if guest root remounts its tag read-write.
Guest targets that equal or contain one another, host directories that equal
or contain one another, and more than two mappings are rejected before boot.
A snapshot captured with mappings requires the same mappings, in the same
order, with the same canonical host paths, targets, modes, and filesystem
identities.
A repeatable --mount-deny HOST_PATH hides an existing file or directory
inside a mapped root. With two mappings, each --mount-deny must be an
absolute path, and it applies to the mapping whose root contains it. Denied
names are omitted from directory listings and remain
inaccessible through .., a symlink/junction, or another mount of the same
virtio-fs device. Unsafe, external, duplicate, overlapping, and nested-mount
rules are rejected before boot.
In an rw mapping, the guest can create symbolic links, and OpenVMM stores
each target exactly as given. The guest resolves links in its own namespace;
OpenVMM never follows a link while resolving a host path, so a link to an
absolute host path, outside the root, or into a denied path cannot reach host
data. An ro mapping rejects link creation with EROFS. On Windows, links
are WSL-style reparse points, which Windows path resolution never follows.
Treat links in a writable share as untrusted when host software later reads
the directory.
By default, OpenVMM performs every guest operation on the mapping as its own
user. On a Linux host, --mount-owner caller instead performs each one as the
guest caller's UID and GID and squashes guest root to the owner of the host
directory, so files that guest root creates are owned by that owner rather
than by root or OpenVMM; see File ownership. A snapshot
captured with a mapping also requires the same --mount-owner mode. Capture
and restore inspect and reopen the guest's open files as OpenVMM's user, so
unless OpenVMM runs as root, they fail while the guest holds a file that only
its caller can reach or reopen, such as one open for writing.
A snapshot captured without a mapping may restore with one new --mount; after
resume, mount it explicitly inside the guest because the initramfs hook has
already completed:
mkdir -p /mnt/host
mount -t virtiofs microvm /mnt/hostThe sandbox command launches the microVM with one to three compressed
EROFS lower layers and one preformatted ext4 scratch image:
python3 scripts/nvx.py build-distro-layer \
--guest ubuntu \
--output build/ubuntu-distro.erofsRead the deterministic UUID from
build/ubuntu-distro.erofs.manifest.json, create scratch independently with
mkfs.ext4, then launch the Ubuntu workload through the existing Alpine
control initramfs:
python3 scripts/nvx.py sandbox \
--layer distro,build/ubuntu-distro.erofs,11111111-1111-1111-1111-111111111111 \
--scratch /var/lib/nvx/scratch.ext4 \
--entrypoint /bin/sh \
--workload-user 65534:65534 \
--memory-mib 256CI uses /sbin/nvx-sandbox-smoke as the entrypoint to verify Ubuntu identity,
the fixed non-root account, and a scratch-backed /tmp write before clean
guest exit. With --arg TARGET --arg ro|rw, it also checks a live share at
TARGET as described below, including symbolic links in an rw share; the
share needs a host-created, world-writable nvx-links directory for them.
Repeat the pair to check several shares; with an rw and an ro share, it
also verifies that a link in the rw share cannot write into the ro share.
With --mount-owner caller, --arg caller performs the rw checks and also
verifies that the workload owns what it creates, populates a directory it
created, and creates links in its own nvx-caller-links directory, while
--arg eperm requires reading and listing the share to fail with EPERM and
writes to fail, which is the result when OpenVMM cannot assume the workload
identity.
The layer UUID is the EROFS superblock UUID, not a content digest. The command
validates the files before launch, orders roles independently of option order,
attaches layers read-only, and reserves the writable slot for scratch.
Conversion and scratch formatting stay off the start path. The Ubuntu
converter verifies immutable inputs, applies the deny-by-default metadata
policy, creates /nonexistent for UID/GID 65534, and invokes mkfs.erofs.
Systemd entrypoints are explicitly unsupported and do not relax the non-root,
drop-all-capabilities sandbox policy.
This is the cold-filesystem bootstrap described in
the sandbox design, not the final
production agent. It accepts no environment variables or secrets and does not
expose sandbox snapshot capture or restore, the configuration region, or runtime RPC.
Arguments are individual kernel-command-line tokens and therefore cannot
contain whitespace. The workload enters private mount/PID/UTS namespaces with
a private /dev, an agent-owned cgroup, no capabilities, and no_new_privs.
It always runs as the fixed non-root UID:GID selected at VM creation
(65534:65534 by default). The guest verifies that exactly one matching user,
its primary group, and its absolute home directory exist in the assembled
root; otherwise the workload is never started.
The outer agent retains the initramfs root; the capability-stripped child
enters only the assembled root with chroot, because Linux cannot
pivot_root away from an initramfs rootfs.
sandbox run and sandbox provision accept up to two
--mount GUEST_TARGET,HOST_PATH[,ro|rw] (default ro) options, each with its
own guest target and access mode, plus repeatable --mount-deny HOST_PATH
rules. OpenVMM exports each host directory through its own microVM virtio-fs
device and enforces its access mode and denied paths on the host side, so
edits are visible in both directions without staging or copy-back:
python3 scripts/nvx.py sandbox \
--layer distro,build/ubuntu-distro.erofs,11111111-1111-1111-1111-111111111111 \
--scratch /var/lib/nvx/scratch.ext4 \
--mount /workspace,/srv/checkout,rw \
--mount-deny .git/credentials \
--mount /opt/hostedtoolcache,/opt/hostedtoolcache,ro \
--entrypoint /bin/shA relative --mount-deny path is resolved inside its share's host directory.
With one share, every --mount-deny applies to it. With two, each
--mount-deny applies to the --mount before it and must name a path inside
that share's directory. The guest targets and the host directories of the two
shares must not equal or contain one another, because one share could
otherwise hide the other or reach its files under a different access mode.
NVX and OpenVMM compare the host directories by resolved path and by file
identity, so one directory reached through two paths, such as a bind mount,
is rejected too. NVX also compares each directory's identity with those of
the other directory's ancestors, so a share inside a bind mount of the other
share's directory is rejected before OpenVMM starts. On Linux, OpenVMM also
compares the filesystem sources that each directory reaches, through the
mount that contains it and every mount below it, so it also rejects a bind
mount or nested mount that exposes part of one share inside the other. That
check is Linux-only, so on Windows, don't share a directory that reaches the
other share's files through a mount.
Guest mount flags are not a security boundary: OpenVMM rejects every write to
an ro share with EROFS, whichever mount or link inside the guest reaches
it.
After it assembles the container overlay and verifies the workload identity,
the guest agent creates each target inside the container root and mounts the
shares there in order with nosuid,nodev before the workload enters its
private mount namespace. A one-shot workload exit, a managed stop, and any
failure after a share is mounted unmount the mounted shares in reverse order
before the overlay is unmounted or the VM powers off. Each target must be an
absolute, canonical path; /, /etc, and
the /proc, /sys, /dev, and /.nvx-agent trees are reserved for the
container runtime.
The guest refuses a target whose path crosses a symbolic link in a container
layer, a repeated tag, and overlapping targets, and any validation or mount
failure aborts the sandbox with status 125 instead of starting the workload
without its shares.
An rw share supports the symbolic links that package managers and
language toolchains create; see
virtio-fs host mapping for their semantics. The
existing OpenVMM file-identity policy applies to each share. A managed
sandbox stores each share's absolute host path, mode, and denied paths and
the ownership mode in its configuration, and reattaches every share on each
start. A configuration with two shares uses format 4, which earlier NVX
releases reject rather than start without the second share.
--mount-owner selects the host identity that performs the share's file
operations:
vmm(the default): OpenVMM performs every operation as its own user, so the files and directories that the workload creates are owned by the OpenVMM user. Guest file permissions use the ownership and mode bits that OpenVMM reports, so grant the workload identity access to the host directory. The workload cannot create entries inside a directory it created unless the host grants write access to others.caller: OpenVMM performs each operation as the guest caller's numeric UID and GID, without supplementary groups or capabilities. Files and directories that the workload creates are owned by its--workload-useridentity on the host, and the host also enforces permissions for that identity, so the workload can populate the directories it creates. Guest UID 0 and GID 0 are squashed to the owner and group of the host directory, which therefore must not be owned by UID 0 or GID 0; the guest can create neither root-owned nor setuid-root host files. If OpenVMM cannot assume a caller's identity, that operation fails withEPERM(Operation not permitted) instead of running as OpenVMM.
python3 scripts/nvx.py sandbox \
--layer distro,/var/lib/nvx/distro.erofs,11111111-1111-1111-1111-111111111111 \
--scratch /var/lib/nvx/scratch.ext4 \
--mount /workspace,/srv/checkout,rw \
--mount-owner caller \
--workload-user 1001:1001 \
--entrypoint /bin/shAs for any sandbox, the layers must define the workload user. Choosing the owner of the host directory as the workload identity keeps every file in it owned by that user.
caller requires a Linux host. Windows has no per-request POSIX identity for
OpenVMM to switch to, so NVX and OpenVMM reject --mount-owner caller on
Windows/WHP. On Linux, assuming an identity other than OpenVMM's own, or
dropping OpenVMM's supplementary groups, needs the CAP_SETUID and
CAP_SETGID capabilities, for example as ambient capabilities of an
unprivileged NVX process. sudo replaces HOME with root's home directory,
so the example passes the user's own back:
sudo setpriv --reuid="$(id -u)" --regid="$(id -g)" --init-groups \
--inh-caps=+setuid,+setgid --ambient-caps=+setuid,+setgid -- \
env HOME="$HOME" python3 scripts/nvx.py sandbox ... --mount-owner callerA service manager can grant the same set, such as systemd's
AmbientCapabilities=CAP_SETUID CAP_SETGID. Without these capabilities, an
operation succeeds only if its caller has OpenVMM's user and primary group and
OpenVMM's user belongs to no other group; every other operation fails with
EPERM. Hosts commonly grant access to /dev/kvm or /dev/mshv through such
a group, so caller usually needs both capabilities. Grant OpenVMM no other
capabilities: CAP_SETUID lets it assume any host identity, which caller
mode confines to its export.
The guest kernel reports each caller's identity, and guest root may assume any
identity inside the guest, so guest root and a compromised guest kernel can act
as any nonzero host UID and GID inside the share, including leaving setuid
files that those identities own. Share only directories that such identities
may modify, and keep the share on a host filesystem mounted nosuid when host
users might execute files from it.
For a state-aware sandbox, provision configuration without starting a VM, start it once, run multiple workloads in the same warm guest, stop it while retaining configuration, and finally deprovision it:
python3 scripts/nvx.py sandbox provision \
--state-dir /run/user/1000/nvx-example \
--layer distro,/var/lib/nvx/distro.erofs,11111111-1111-1111-1111-111111111111 \
--scratch /var/lib/nvx/scratch.ext4
python3 scripts/nvx.py sandbox start \
--state-dir /run/user/1000/nvx-example
python3 scripts/nvx.py sandbox exec \
--state-dir /run/user/1000/nvx-example \
--entrypoint /usr/bin/python3 --arg=/work/agent.py --cwd /work \
--environment-file /run/user/1000/nvx-example-environment.json \
--outcome-report /run/user/1000/nvx-example-exec.json
python3 scripts/nvx.py sandbox exec \
--state-dir /run/user/1000/nvx-example \
--entrypoint /bin/sh --arg=-c --arg='cat /tmp/previous-result'
python3 scripts/nvx.py sandbox stop \
--state-dir /run/user/1000/nvx-example
python3 scripts/nvx.py sandbox deprovision \
--state-dir /run/user/1000/nvx-exampleLifecycle transitions fail closed: start rejects an already-running or stale
runtime record, exec and stop require a live OpenVMM process, and
deprovision refuses to remove a running sandbox or unknown files. The runtime
record identifies OpenVMM by its process ID and start time, so these checks
treat OpenVMM as gone once it exits, even if no process reaps it or another
process reuses its ID. A record that an earlier NVX version wrote lacks the
start time and identifies OpenVMM by its process ID alone. Managed
workload arguments use the bounded control protocol rather than the kernel
command line and may contain whitespace. Managed execution can select an
absolute working directory and either repeated inline KEY=VALUE entries or a
UTF-8 JSON-array environment file. The two environment forms are mutually
exclusive. Omission inherits the guest bootstrap environment, not the host
environment: PATH=/usr/sbin:/usr/bin:/sbin:/bin, TERM=linux, and HOME,
USER, and LOGNAME resolved from the fixed workload identity. An empty file
array requests an empty environment. --inherit-default-environment layers
the supplied entries over that default environment instead of replacing it,
each entry replacing the default variable of the same name; without supplied
entries it has no effect. Inline values are visible in host process arguments
and should not be used for secrets. These options apply only to managed
sandbox exec; one-shot execution rejects them. The workload sees one machine
ID for the life of the VM. On stop, the guest agent unmounts the live share,
overlay, layers, and scratch in dependency order before the VM powers off, as it
does when a one-shot workload exits. The legacy operation-less sandbox
form is sandbox run; it remains one-shot and rejects --state-dir or any
request to retain VM state.
Environment files are limited to 1 MiB of UTF-8 JSON. Environments contain at
most 256 unique, nonempty names. Each KEY=VALUE entry and working-directory
path is limited to 4096 UTF-8 bytes; the combined execution request must also
fit the existing 64 KiB control-payload bound.
The explicit test-microvm --scenario managed-exec-config --backend BACKEND
scenario is the authoritative acceptance for these public options. It invokes
scripts/nvx.py sandbox provision, start, exec, stop, and deprovision
as subprocesses with an Alpine control guest and an Ubuntu workload layer. It
checks sequential distinct CWD and exact-environment requests, an environment
layered over the defaults, and then omitted defaults. It resolves the selected
UID 65534 account from the Ubuntu workload's own passwd database through a
public managed getent execution, then requires exactly the documented PATH,
TERM, HOME, USER, and LOGNAME values, which a layered entry replaces
only in its own execution. Unrelated guest bootstrap and shell-provided entries are
permitted because omission inherits the guest bootstrap environment; prior
request entries and the internal execution-config descriptor must not leak.
It also checks public
rejection of relative CWD and out-of-range uint32
timeouts, timeout recovery, stdout/stderr forwarding, and typed exec outcome
reports. It requires build/ubuntu-distro.erofs, its manifest, and the
build/ubuntu-smoke-scratch.ext4 template produced by the guest-artifact build.
CI invokes it separately on every backend; it is not included in the default
scenario set because downloaded packages do not include the scratch template.
Empty environments are measured with /usr/bin/env, not a shell that can
synthesize its own variables. The scenario retains bounded subprocess argument
and status observations, typed exec outcomes, and OpenVMM logs. Inline
environment values are redacted from the retained command observations.
Decoder, helper, and direct control-session tests remain useful supplemental
coverage for protocol boundaries and guest implementation details. They do not
replace or establish support through the public nvx.py sandbox interface.
run --outcome-report PATH and one-shot sandbox run --outcome-report PATH
forward OpenVMM's bounded local JSON report. Managed sandbox exec writes only
the operation, bounded result category, numeric status, and an opaque operation
ID to its requested report; stdout, stderr, arguments, environment values, and
credentials remain excluded. sandbox stop waits for OpenVMM teardown and
retains the latest VM-level report as outcome.json in the state directory.
Neither OpenVMM nor NVX uploads these files.