USB Kill Switch for Qubes OS - Physical Security Enhancement

This guide explains how to set up a USB-based kill switch for Qubes OS that locks your screen immediately and shuts down your system shortly after a specific USB device is removed.
It’s similar to BusKill, but purpose-built for Qubes OS.
ONLY TESTED FOR XFCE ENVIRONMENT WITH THE XFCE SCREENSAVER

What it does:

  • Monitors a chosen USB device (flash drive, hardware token, etc.)
  • Locks screen immediately if device is disconnected
  • Shuts down after a set delay if screen remains locked
  • Resumes monitoring if you unlock before shutdown

Prerequisites

  • Qubes OS (tested on 4.2.4)
  • A USB device to act as your “key”
  • Basic terminal knowledge

Installation

1. Create Script Directory

mkdir -p ~/Scripts
cd ~/Scripts

2. Create the Scripts

SelectUSB.sh – Choose USB device to monitor

#!/bin/bash
ConfigDir="$HOME/Scripts"
mkdir -p "$ConfigDir"

ListDevices() {
    echo "===== Available USB Block Devices ====="
    Devices=()
    Types=()
    Index=1
    while IFS= read -r Line; do
        BackendDevice=$(echo "$Line" | awk '{print $1}')
        Description=$(echo "$Line" | cut -d' ' -f3-)
        Devices+=("$BackendDevice")
        Types+=("block")
        echo "$Index) $BackendDevice  -  $Description"
        ((Index++))
    done < <(qvm-block list 2>/dev/null | tail -n +2)

    echo
    echo "===== Available Raw USB Devices ====="
    while IFS= read -r Line; do
        BackendDevice=$(echo "$Line" | awk '{print $1}')
        Description=$(echo "$Line" | cut -d' ' -f3-)
        Devices+=("$BackendDevice")
        Types+=("usb")
        echo "$Index) $BackendDevice  -  $Description"
        ((Index++))
    done < <(qvm-usb list 2>/dev/null | tail -n +2)
}

ChooseDevice() {
    while true; do
        clear
        echo "USB Kill Switch - Device Selection"
        echo "=================================="
        ListDevices
        echo
        echo "Enter number to select device, 'r' to refresh, or 'q' to quit:"
        read -rp "> " Choice
        case "$Choice" in
            "r"|"R") continue ;;
            "q"|"Q") exit 0 ;;
            *)
                if [[ "$Choice" =~ ^[0-9]+$ ]] && (( Choice >= 1 && Choice <= ${#Devices[@]} )); then
                    SelectedDevice="${Devices[$((Choice-1))]}"
                    MonitorType="${Types[$((Choice-1))]}"
                    MonitorVM=$(echo "$SelectedDevice" | cut -d':' -f1)
                    MonitorDevice=$(echo "$SelectedDevice" | cut -d':' -f2)
                    echo
                    echo "Selected: $MonitorDevice from $MonitorVM (type: $MonitorType)"
                    echo "Press Enter to confirm..."
                    read
                    break
                else
                    echo "Invalid choice! Try again."
                    sleep 2
                fi
                ;;
        esac
    done
}

echo "Starting USB Kill Switch setup..."
ChooseDevice
echo "MonitorType=$MonitorType" > "$ConfigDir/USBDevice.conf"
echo "MonitorVM=$MonitorVM" >> "$ConfigDir/USBDevice.conf"
echo "MonitorDevice=$MonitorDevice" >> "$ConfigDir/USBDevice.conf"

echo "Configuration saved. Starting USB Kill Switch service..."
systemctl --user start monitor-drive-usbkill.service
echo "USB Kill Switch is now active!"
sleep 3

Tip:
If you want this selector to appear automatically after login (e.g., so you can choose your USB key every session), create a .desktop file in ~/.config/autostart:

[Desktop Entry]
Type=Application
Name=USB Kill Switch Setup
Exec=xfce4-terminal --command "/home/user/Scripts/SelectUSB.sh"
Terminal=true
X-GNOME-Autostart-enabled=true

Replace /home/user with your actual username.


USBKill.sh – Main monitoring daemon

#!/bin/bash
ConfigDir="$HOME/Scripts"
ConfigFile="$ConfigDir/USBDevice.conf"

if [[ ! -f "$ConfigFile" ]]; then
    echo "ERROR: No USB device configured."
    echo "Please run SelectUSB.sh first."
    exit 1
fi

source "$ConfigFile"

ShutdownTimeout=10
CheckInterval=0.5
DisconnectedTime=0

LogMessage() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | systemd-cat -t usbkill
}

IsDeviceConnected() {
    if [[ "$MonitorType" == "block" ]]; then
        qvm-block list "$MonitorVM" 2>/dev/null | grep -q "$MonitorDevice"
    else
        qvm-usb list "$MonitorVM" 2>/dev/null | grep -q "$MonitorDevice"
    fi
}

IsScreenLocked() {
    xscreensaver-command -time 2>/dev/null | grep -q "screen locked"
}

LockScreen() {
    if ! IsScreenLocked; then
        LogMessage "Locking screen due to USB disconnect"
        xflock4 &
    fi
}

LogMessage "USB Kill Switch started - Monitoring $MonitorType device $MonitorDevice on $MonitorVM"

while true; do
    if [[ $DisconnectedTime -eq 0 ]]; then
        if ! IsDeviceConnected; then
            DisconnectedTime=$(date +%s)
            LogMessage "USB device disconnected"
            LockScreen
        fi
    else
        CurrentTime=$(date +%s)
        ElapsedTime=$((CurrentTime - DisconnectedTime))

        if ! IsScreenLocked; then
            LogMessage "Screen unlocked — stopping service"
            systemctl --user stop monitor-drive-usb.service
            xfce4-terminal -x "$ConfigDir/SelectUSB.sh"
            exit 0
        fi

        if [[ $ElapsedTime -ge $ShutdownTimeout ]]; then
            LogMessage "Timeout reached — shutting down"
            shutdown now
            exit 0
        fi
    fi
    sleep $CheckInterval
done

3. Set Permissions

chmod +x ~/Scripts/*.sh

4. Create Systemd Service

File: ~/.config/systemd/user/monitor-drive-usbkill.service

[Unit]
Description=USB Kill Switch Monitor
After=graphical-session.target
Wants=graphical-session.target

[Service]
Type=simple
ExecStart=%h/Scripts/USBKill.sh
Restart=on-failure
RestartSec=10
Environment=DISPLAY=:0

[Install]
WantedBy=default.target

Usage

  1. Run:
~/Scripts/SelectUSB.sh
  1. Pick your USB key
  2. Service runs automatically and monitors the device

Customization

  • ShutdownTimeout=30 – seconds before shutdown
  • CheckInterval=0.5 – polling interval in seconds

Troubleshooting

  • Check logs:
journalctl --user -u monitor-drive-usbkill.service --no-pager
  • Make sure xscreensaver is running for screen lock

Uninstall

systemctl --user stop monitor-drive-usbkill.service
rm -rf ~/Scripts/
rm ~/.config/systemd/user/monitor-drive-usbkill.service
systemctl --user daemon-reload
6 Likes

Buskill has a Qubes specific guide

I never presume to speak for the Qubes team.
When I comment in the Forum I speak for myself.

3 Likes

I know BusKill’s Qubes guide. This is for people who don’t have BusKill. It works with any external device (USB, SD, external SSD) and is built specifically for Qubes OS.

2 Likes

@juicewrld thanks for sharing :slight_smile:

To be clear: BusKill was originally designed specifically for QubesOS. When I created BusKill in 2017, QubesOS was my main OS (and it still is today).

And BusKill also works with any USB device. We do sell an open-hardware BusKill cable on our website, but that’s just to lower the barrier of entry and make it more accessible to non-techie folks (eg many journalists, whistleblowers, human rights workers, etc).

Anyone can make their own BusKill cable.

The cross-platform software and our 2-part QubesOS guide will work with any USB device.

For more information, please see our documentation:

Please feel free to submit a feature request, if you think that BusKill could be improved for Qubes.

1 Like

A few months ago, I found a system that does almost the same thing but with a different approach and I use a usb key specially dedicated to that (an old 64MB usb key).

The method is different in the sense that I create a rule in sys-usb then, in dom0 a .policy rule and a small script that allows to define the action to execute in case of removal of the key.
It was inspired by an article @solene : Solene'% : How to trigger a command on a running Linux laptop when disconnected from power

I take advantage of this topic (I had not seen it before! ) to post this approach.

  1. In sys-usb:
  • Create a rule in /etc/udev/rules.d/ in sys-usb:

    sudo vim /etc/udev/rules.d/99-usb-remove.rules

    Add that in it :

    ACTION=="remove", SUBSYSTEM=="block", ENV{ID_SERIAL}=="Numero_de_série", RUN+="/usr/bin/qrexec-client-vm dom0 qubes.USBRemoved"

    To find the serial number, identify the device with fdisk -l (in this example: /dev/sdX) and type the command:

    udevadm info --query=all --name=/dev/sdX

    Search the section: ID_SERIAL to find the serial number.

  • recharge the rules with :
    sudo udevadm control --reload-rules

  1. In Dom0 :
  • Create a script to define the action :

    sudo vim /usr/local/bin/usb_removed_action.sh
    sudo chmod +x /usr/local/bin/usb_removed_action.sh

    For example to suspend to system :

    #!/bin/bash
    systemctl suspend
    
  • Create a file in the directory /etc/qubes/policy.d :

    sudo vim /etc/qubes/policy.d/50-usb-remover.policy

    with the content:

    qubes.USBRemoved * sys-usb dom0 allow

Now, by disconnecting the key, the command of the /usr/local/bin/usb_removed_action.sh file will be activated.

3 Likes