Is it safe to store a VM's command output into a dom0 variable? (Sanitizing variables in dom0)

I have a question regarding the security of storing a VM’s output into a dom0 variable.

I wrote a shell script in dom0 to automate my backup process. It interacts with a backup VM (sys-backup) and a USB VM (sys-usb) with a YubiKey. To make the automation work, the script captures the command output from the VM (like encryption keys or backup targets) and stores it into a dom0 variable, which is then passed as an argument to subsequent dom0 backup commands (like qvm-backup).

However, I am feeling a bit anxious about this approach. Conceptually, storing a VM’s output into a dom0 variable feels almost identical to copy-pasting untrusted data directly into dom0. If the VM were to be compromised, this could potentially open the door to a command injection attack or shell exploitation in dom0.

My questions are:

  1. Is this variable assignment pattern inherently dangerous in Qubes OS?
  2. Since I absolutely need these variables to run dom0-specific backup commands, is there any way to make this process safer?
  3. How can I properly sanitize or validate the VM’s output within dom0 before storing it as a variable to prevent any malicious shell execution?

Here is the script

#!/bin/bash
qvm-shutdown --all --wait
qvm-start sys-backup sys-usb

sleep 30

YUBIKEY=$(qvm-usb l | grep “Yubikey” | awk ‘{print $1}’)
USBS=$(qvm-block l | grep “Storage” | awk ‘{print $1}’)

BACKUP_VMS=$(qvm-ls --raw-list | grep -E “^(vault|anon-whonix)”)
BACKUP_PW=“changeme”
echo $BACKUP_PW | qvm-backup --dest-vm sys-backup --passphrase-file - -y /home/user/ $BACKUP_VMS

SYS_BACKUP_PATH=$(qvm-run -p sys-backup “echo qubes-backup-*”)
USB_BACKUP_PATH=/mnt/$SYS_BACKUP_PATH

qvm-usb attach sys-backup $YUBIKEY

for i in ${USBS[@]}; do
qvm-block attach sys-backup $i

qvm-run -p sys-backup '
	YK_CHALLENGE="changeme"
	LUKS_PW=$(ykman otp calculate 2 "$(echo -n "$YK_CHALLENGE" | xxd -p)" |tr -d "\n" )
	printf "%s" "$LUKS_PW" | sudo cryptsetup luksOpen /dev/xvdi backup_usb --key-file -
	sudo mount /dev/mapper/backup_usb /mnt
	BACKUP_PATH=$(echo /home/user/qubes-backup-*)
	cp "$BACKUP_PATH" /mnt/
	sudo sync'

USB_NAME=$(qvm-block l | grep $i | awk '{print $6,$7,$8}')

if echo $BACKUP_PW | qvm-backup-restore --dest-vm sys-backup --verify-only --passphrase-file - $USB_BACKUP_PATH; then
	notify-send "Backup $USB_NAME: SUCCESS & VERIFIED" -u critical -i dialog-information
else
	notify-send "Backup $USB_NAME: VERIFICATION FAILED" -u critical -i error
fi

qvm-run -p sys-backup '
	sudo umount /mnt
	sudo cryptsetup luksClose backup_usb'

qvm-block detach sys-backup $i

done

qvm-shutdown --all --wait
qvm-start sys-usb sys-whonix

Check your script with shellcheck. You will see lots of things to fix.

General rule: do not use variable names in caps as you can override environment variables by mistake.

As for the actual question: it is safe if the command output is safe, i.e. take care of proper error handling and output validation.

foo="$(qvm-run -p ...)" is not inherently dangerous. (Assumption: no ancient, exploitable pipe → buffer bug in bash.)

You could write a simple qrexec API with strict constraints on arguments and input/output (if you write the API that way) to do a safer, checked equivalent of qvm-run. You could set up /etc/qubes/policy.d permissions to offload some of the work to an intermediary “backup management” VM, for less direct involvement of dom0.

After doing foo="$(qvm-run -p ...)", "$foo" is just a string. Always keep it quoted. grep bar <<< "$foo" is safe. [[ "$foo" =~ bar ]] is safe, stuff like "${foo/baz/qux}" is safe. Be careful about using it in a context where it could be interpreted as a command flag, e.g. echo "$foo" | ... is a little suss.