Execute commands in Qubes OS dom0 from one or more VMs.
SSH for dom0 — qvm-remote qvm-ls is to dom0 what ssh host ls is
to a remote server. Written in Python following official qvm-* tool
conventions.
Warning: This tool grants VMs the ability to execute arbitrary commands in dom0 with root privileges. This breaks the Qubes security model by design. Only use it on machines you fully control.
VM (visyble) dom0
┌────────────────────┐ ┌──────────────────────┐
│ │ │ │
│ qvm-remote │ qvm-run │ qvm-remote-dom0 │
│ "qvm-ls" │◄────────────►│ polls queue │
│ │ │ --pass-io │ verifies HMAC key │
│ ▼ │ │ executes command │
│ ~/.qvm-remote/ │ │ returns results │
│ queue/ │ │ │
│ auth.key │ │ /etc/qubes/remote.d/ │
│ history/ │ │ visyble.key │
│ audit.log │ │ dev-vm.key │
└────────────────────┘ └──────────────────────┘
- VM client writes a command + HMAC-SHA256 auth token to
~/.qvm-remote/queue/pending/ - Dom0 daemon polls authorized VMs via
qvm-run --pass-io --no-autostart - Verifies the HMAC token against the VM's registered key
- Fetches, validates, and executes the command with
bash - Writes stdout, stderr, and exit code back to the VM
- VM client outputs results and archives everything to
history/
Every command is logged with timestamp, duration, and exit code on both sides.
sudo make install-vm
qvm-remote key gen # generates 256-bit auth keyFrom a dom0 terminal, use the installer:
VM=visyble
qvm-run --pass-io --no-gui $VM \
'cat /path/to/qvm-remote/install/install-dom0.sh' \
> /tmp/install-dom0.sh
bash /tmp/install-dom0.sh $VMOr install manually:
VM=visyble
# Pull daemon
qvm-run --pass-io --no-gui $VM \
'cat /usr/bin/qvm-remote-dom0' > /usr/bin/qvm-remote-dom0
chmod +x /usr/bin/qvm-remote-dom0
# Pull service file
qvm-run --pass-io --no-gui $VM \
'cat /path/to/dom0/qvm-remote-dom0.service' \
> /etc/systemd/system/qvm-remote-dom0.service
# Configure
echo "QVM_REMOTE_VMS=$VM" > /etc/qubes/remote.conf
chmod 0600 /etc/qubes/remote.conf
# Authorize the VM's key
KEY=$(qvm-run --pass-io --no-gui $VM 'cat ~/.qvm-remote/auth.key')
qvm-remote-dom0 authorize $VM $KEY
# Start
systemctl daemon-reload
systemctl start qvm-remote-dom0qvm-remote ping # "qvm-remote-dom0 is responding."
qvm-remote hostname # "dom0"# Run any command in dom0 (like ssh)
qvm-remote qvm-ls
qvm-remote 'qvm-prefs work memory 4096'
qvm-remote -t 60 'qvm-shutdown --wait work'
echo 'xl info' | qvm-remote
qvm-remote < deploy.sh
# Key management
qvm-remote key gen # generate + store key
qvm-remote key show # print key for dom0
qvm-remote key import KEY # import a hex key
# Diagnostics
qvm-remote ping # health check
qvm-remote log # last 20 audit entries
qvm-remote log 50 # last 50 entries
qvm-remote history # last 10 commands# Key management
qvm-remote-dom0 authorize VM KEY # register a VM
qvm-remote-dom0 revoke VM # remove a VM
qvm-remote-dom0 keys # list authorized VMs
# Service control
systemctl start qvm-remote-dom0 # this session only
journalctl -u qvm-remote-dom0 # view logs
qvm-remote-dom0 enable # autostart (risk prompt)
qvm-remote-dom0 disable # stop and disable
qvm-remote-dom0 --once # process queue once
qvm-remote-dom0 --dry-run --once # preview without executingqvm-remote uses 256-bit HMAC-SHA256 key authentication — analogous to SSH keys. Each VM has its own unique key. No passwords, PINs, or retries.
- Each VM holds a 256-bit (64-hex-char) secret key in
~/.qvm-remote/auth.key - Dom0 holds a copy in
/etc/qubes/remote.d/<vm-name>.key - Every command carries
HMAC-SHA256(key, command_id)as a token - Dom0 recomputes the HMAC and verifies before executing
- Invalid tokens are silently rejected and logged
- 256-bit key: 2^256 possible keys makes brute force mathematically impossible
- Per-command HMAC: each token is unique — replay is useless
- Key never transmitted: only the HMAC token travels; key lives on disk (mode 0600)
- No retries/lockout needed: with 256 bits, even unlimited attempts are futile
- Per-VM isolation: compromising one VM's key does not affect others
Dom0 can poll multiple VMs simultaneously:
# /etc/qubes/remote.conf
QVM_REMOTE_VMS="visyble dev-vm staging"Each VM must have its own key:
qvm-remote-dom0 authorize visyble <key1>
qvm-remote-dom0 authorize dev-vm <key2>
qvm-remote-dom0 authorize staging <key3>/etc/qubes/remote.conf in dom0:
QVM_REMOTE_VMS="visyble dev-vm"Per-VM keys in /etc/qubes/remote.d/:
/etc/qubes/remote.d/
├── visyble.key # 64-hex-char key (mode 0600)
└── dev-vm.key # 64-hex-char key (mode 0600)
The VM client requires no configuration beyond the key file.
make docker-rpm # builds in Fedora 41 containermake dist # create source tarballs
make rpm # build RPMs (requires rpmbuild)Add to your builder.yml:
components:
- qvm-remote:
url: https://github.com/USER/qvm-remote.git
branch: main
verification-mode: insecure-skip-checkingBuild: ./qb -c qvm-remote package fetch prep build
make check # syntax-check all scripts (including GUI)
make test # run full test suite (115 tests)
make docker-test # RPM install test (Fedora 41)
make dom0-test # dom0 simulation E2E
make fedora-test # Fedora client + dom0 + GUI
make arch-test # Arch Linux client + GUI
make gui-test # GUI build/import test
make distro-test # all distro tests (Fedora + Arch)It grants authorized VMs full root-level command execution in dom0.
HMAC-SHA256 key auth. 256-bit per-VM keys. Mathematically impossible
to brute-force. Python hmac module — compatible with openssl for
cross-version interop.
Transient by default. Service stops on reboot. enable requires
typing "yes" to an explicit risk warning.
Multi-VM isolation. Each VM has its own key. Revoking one doesn't affect others.
No VM auto-start. Uses qvm-run --no-autostart — never starts VMs
as a side effect.
Full audit trail. Both sides log every command:
- VM:
~/.qvm-remote/audit.log - Dom0:
/var/log/qubes/qvm-remote.log - Full output:
~/.qvm-remote/history/YYYY-MM-DD/
Input validation. Rejects empty, oversized (>1 MiB), and binary commands.
Output limits. Stdout/stderr truncated at 10 MiB.
- Use dedicated, minimal VMs with no network access.
- Stop the service when not in use.
- Review
journalctl -u qvm-remote-dom0periodically. - Rotate keys periodically (
key gen+authorize).
v1.0.0 renames the project and rewrites everything in Python:
| v0.x (bash) | v1.0.0 (Python) |
|---|---|
qubes-remote |
qvm-remote |
qubes-remote-dom0 |
qvm-remote-dom0 |
~/.qubes-remote/ |
~/.qvm-remote/ |
--gen-key |
key gen |
--show-key |
key show |
--import-key KEY |
key import KEY |
--authorize VM KEY |
authorize VM KEY |
--revoke VM |
revoke VM |
--list-keys |
keys |
QUBES_REMOTE_VMS= |
QVM_REMOTE_VMS= |
Data directories migrate automatically on first run. RPM packages use
Obsoletes: for clean upgrades. Config variables are backward compatible.
Both the VM client and dom0 service have optional GTK3 graphical interfaces. They follow the Qubes OS visual style guide and interact with the CLI tools via subprocess calls (no code coupling).
Tabbed interface with: Execute, Files, Backup, History, Keys, Log.
sudo make install-gui-vm
qvm-remote-guiService manager with: Dashboard, Virtual Machines, Backup, Log.
sudo make install-gui-dom0
sudo qvm-remote-dom0-guiRequirements: python3-gobject and gtk3 (Fedora) or python-gobject and gtk3 (Arch).
- Qubes OS 4.2+ (tested on 4.3)
- Python 3.8+ in both dom0 and VMs
- GTK3 + PyGObject (optional, for GUI only)
qvm-remote/
├── .github/
│ └── workflows/ci.yml GitHub Actions CI
├── dom0/
│ ├── qvm-remote-dom0 Dom0 daemon (Python)
│ └── qvm-remote-dom0.service Systemd unit
├── gui/
│ ├── qubes_remote_ui.py Shared GTK3 UI module
│ ├── qvm-remote-gui VM-side GUI (GTK3)
│ ├── qvm-remote-dom0-gui Dom0-side GUI (GTK3)
│ ├── qvm-remote-gui.desktop VM desktop entry
│ └── qvm-remote-dom0-gui.desktop Dom0 desktop entry
├── vm/
│ └── qvm-remote VM client (Python)
├── etc/
│ └── qubes-remote.conf Config template
├── install/
│ └── install-dom0.sh Dom0 shell installer
├── pkg/
│ └── PKGBUILD Arch Linux package
├── rpm_spec/
│ ├── qvm-remote-dom0.spec Dom0 RPM spec
│ ├── qvm-remote-vm.spec VM RPM spec
│ ├── qvm-remote-gui-dom0.spec Dom0 GUI RPM spec
│ └── qvm-remote-gui-vm.spec VM GUI RPM spec
├── salt/
│ ├── README.md Salt deployment guide
│ ├── qvm-remote/init.sls Salt state
│ ├── qvm-remote.top Salt top file
│ └── pillar/qvm-remote.sls Salt pillar
├── test/
│ ├── Dockerfile.arch-client Arch Linux client + GUI tests
│ ├── Dockerfile.dom0-sim Dom0 E2E simulation
│ ├── Dockerfile.fedora-client Fedora client + dom0 + GUI tests
│ ├── Dockerfile.gui GUI build/import test
│ ├── Dockerfile.test RPM install test
│ ├── dom0-sim/ Mock Qubes tools for E2E
│ └── test_qvm_remote.py Python test suite (115 tests)
├── .qubesbuilder Qubes Builder v2 config
├── CONTRIBUTING.md Contribution guidelines
├── Dockerfile.build Fedora 41 build container
├── Makefile Build system
├── Makefile.builder Qubes Builder integration
├── version
└── README.md
See CONTRIBUTING.md for development workflow, code style, security guidelines, test conventions, and RPM signing instructions.