A Raspberry Pi USB reset box for safely wiping and reformatting presentation flash drives in a semi-isolated workflow before re-use.
The device waits for a USB flash drive, requires a long button press, formats the drive as a standard Windows-compatible exFAT volume, copies autorun.inf and favicon.ico to the root of the drive, and uses a traffic-light LED module to show state.
Important: this project is a convenience and risk-reduction tool. It does not provide 100% protection against malware, firmware-level attacks, malicious USB devices, BadUSB-style HID attacks, etc.
No USB drive inserted:
Hold button for 5 seconds -> safe Raspberry Pi shutdown
USB drive inserted:
Yellow LED -> USB drive detected, waiting for confirmation
Hold button for 3 seconds -> wipe and format USB drive
Red LED -> formatting, do not remove the drive
Green LED -> done, safe to remove the drive
The resulting USB flash drive is created as:
Partition table: MBR / msdos
Filesystem: exFAT
Volume label: IPHYS CAS
Root files: autorun.inf, favicon.ico
autorun.inf is used only to set the drive label and icon in Windows Explorer. Modern Windows versions do not use USB autorun.inf to automatically run arbitrary programs from removable USB drives, which is a security feature.
Example autorun.inf:
[autorun]
icon=favicon.ico
label=IPHYS CASThis project was inspired by:
- HiroYokoyama, usb-drive-formatting-system: https://github.com/HiroYokoyama/usb-drive-formatting-system
Main differences in this build:
- formats as exFAT instead of FAT32;
- copies
autorun.infandfavicon.icoafter formatting; - uses a Pi-Stop traffic-light module with red/yellow/green states;
- adds a 5-second safe shutdown function when no USB drive is present;
- adds extra safety checks for USB device type and maximum drive size.
Parts used in this build were sourced from RPishop.cz. Equivalent parts from other suppliers should work.
| Part | Purpose | Example link |
|---|---|---|
| Raspberry Pi 4B starter kit, 4GB, case, 64GB microSD, power supply | Main computer | https://rpishop.cz/280602/raspberry-pi-4b-oficialni-star-t-box-4gb/ |
| Pi-Stop SC13675 traffic light for Raspberry Pi | Red/yellow/green status indication | https://rpishop.cz/led/1395-pi-stop-sc13675.html |
| Female/Male jumper wires, 20 cm, 10 pcs | Wiring | https://rpishop.cz/dratove-propojky/5684-dratove-propojky-femalemale-20-cm-10-ks.html |
| 12 mm metal push button switch | Larger panel-mounted button option | https://rpishop.cz/557903/kovovy-tlacitkovy-spinac-12-mm/ |
| Low-cost USB flash drives | Presentation media to be reformatted | any standard USB flash drive |
Useful pinout references:
- Raspberry Pi GPIO pinout: https://pinout.xyz/
- Pi-Stop traffic-light pinout reference: https://pinout.zerostem.io/pinout/pi_stop
This build uses BCM GPIO numbering in Python.
| Function | BCM GPIO | Physical pin | Notes |
|---|---|---|---|
| Pi-Stop green LED | GPIO17 | pin 11 | Done / safe to remove |
| Pi-Stop yellow LED | GPIO27 | pin 13 | Waiting / ready |
| Pi-Stop red LED | GPIO22 | pin 15 | Formatting / error |
| Pi-Stop GND | GND | pin 9 | Common ground |
| Button signal | GPIO16 | pin 36 | Button input |
| Button ground | GND | pin 34 | Button ground |
Button wiring:
button contact 1 -> physical pin 36 / GPIO16
button contact 2 -> physical pin 34 / GND
The button is read with the internal pull-up resistor enabled. The GPIO state is therefore:
not pressed -> HIGH
pressed -> LOW
flowchart TD
A[Power on Raspberry Pi] --> B[Idle]
B --> C{USB drive inserted?}
C -- No --> D{Button held 5 s?}
D -- Yes --> E[Safe shutdown]
D -- No --> B
C -- Yes --> F[Start usb-format service]
F --> G[Yellow LED: waiting]
G --> H{Button held 3 s?}
H -- No --> G
H -- Yes --> I[Red LED: wipe and format]
I --> J[Create MBR partition table]
J --> K[Create one exFAT partition]
K --> L[Copy autorun.inf and favicon.ico]
L --> M[Set hidden/system attributes if fatattr is available]
M --> N[Unmount and power off USB device]
N --> O[Green LED: done]
This guide assumes Raspberry Pi OS is already installed on the microSD card. A Lite installation is sufficient, but the Desktop version also works.
Boot the Raspberry Pi and open a terminal.
Update the OS:
sudo apt update
sudo apt full-upgrade -y
sudo rebootAfter reboot, install required packages:
sudo apt update
sudo apt install -y python3-rpi.gpio dosfstools exfatprogs parted util-linux udisks2 nano fatattrfatattr is optional but useful. It allows the script to mark autorun.inf and favicon.ico as hidden/system on FAT/exFAT filesystems. If it is not available on your distribution, the formatter still works; the files may simply remain visible in Windows.
Create these files on the Raspberry Pi:
/usr/local/bin/format_usb.py
/usr/local/bin/button_shutdown_monitor.py
/etc/systemd/system/usb-format@.service
/etc/systemd/system/button-shutdown-monitor.service
/etc/udev/rules.d/99-usb-format.rules
/opt/usb-formatter/assets/autorun.inf
/opt/usb-formatter/assets/favicon.ico
Create the asset directory:
sudo mkdir -p /opt/usb-formatter/assetsCopy your icon and autorun file into it:
sudo cp autorun.inf /opt/usb-formatter/assets/autorun.inf
sudo cp favicon.ico /opt/usb-formatter/assets/favicon.icoVerify:
ls -l /opt/usb-formatter/assets
cat /opt/usb-formatter/assets/autorun.infCreate a quick test file in your home directory:
nano ~/gpio_test.pyPaste:
#!/usr/bin/env python3
import time
import RPi.GPIO as GPIO
PIN_GREEN = 17
PIN_YELLOW = 27
PIN_RED = 22
PIN_BUTTON = 16
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PIN_GREEN, GPIO.OUT)
GPIO.setup(PIN_YELLOW, GPIO.OUT)
GPIO.setup(PIN_RED, GPIO.OUT)
GPIO.setup(PIN_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
try:
while True:
GPIO.output(PIN_RED, GPIO.HIGH)
print("Red on")
time.sleep(0.5)
GPIO.output(PIN_RED, GPIO.LOW)
GPIO.output(PIN_YELLOW, GPIO.HIGH)
print("Yellow on")
time.sleep(0.5)
GPIO.output(PIN_YELLOW, GPIO.LOW)
GPIO.output(PIN_GREEN, GPIO.HIGH)
print("Green on")
time.sleep(0.5)
GPIO.output(PIN_GREEN, GPIO.LOW)
if GPIO.input(PIN_BUTTON) == GPIO.LOW:
print("Button pressed")
else:
print("Button not pressed")
time.sleep(0.5)
except KeyboardInterrupt:
GPIO.output(PIN_RED, GPIO.LOW)
GPIO.output(PIN_YELLOW, GPIO.LOW)
GPIO.output(PIN_GREEN, GPIO.LOW)
GPIO.cleanup()Make it executable and run it:
chmod +x ~/gpio_test.py
python3 ~/gpio_test.pyExpected behavior:
red LED blinks
yellow LED blinks
green LED blinks
pressing the button prints: Button pressed
Stop with Ctrl+C.
Create the main script:
sudo nano /usr/local/bin/format_usb.pyPaste:
#!/usr/bin/env python3
import os
import re
import sys
import time
import signal
import shutil
import subprocess
import RPi.GPIO as GPIO
PIN_GREEN = 17
PIN_YELLOW = 27
PIN_RED = 22
PIN_BUTTON = 16
HOLD_SECONDS = 3.0
MAX_SIZE_GB = 256
FS_LABEL = "IPHYS CAS"
ASSET_DIR = "/opt/usb-formatter/assets"
AUTORUN_SRC = os.path.join(ASSET_DIR, "autorun.inf")
ICON_SRC = os.path.join(ASSET_DIR, "favicon.ico")
MOUNT_DIR = "/mnt/usb-formatter-target"
def run(cmd, check=True):
print("+ " + " ".join(cmd), flush=True)
return subprocess.run(
cmd,
check=check,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
def set_lights(red=False, yellow=False, green=False):
GPIO.output(PIN_RED, GPIO.HIGH if red else GPIO.LOW)
GPIO.output(PIN_YELLOW, GPIO.HIGH if yellow else GPIO.LOW)
GPIO.output(PIN_GREEN, GPIO.HIGH if green else GPIO.LOW)
def blink(red=False, yellow=False, green=False, times=3, delay=0.25):
for _ in range(times):
set_lights(red=red, yellow=yellow, green=green)
time.sleep(delay)
set_lights()
time.sleep(delay)
def cleanup(signum=None, frame=None):
set_lights()
GPIO.cleanup()
sys.exit(0)
def get_device_from_arg(arg):
if arg.startswith("/dev/"):
dev = arg
else:
dev = "/dev/" + arg
if not re.fullmatch(r"/dev/sd[a-z]", dev):
raise RuntimeError(f"Refusing suspicious device name: {dev}")
return dev
def is_usb_device(dev):
name = os.path.basename(dev)
sys_path = f"/sys/block/{name}"
if not os.path.exists(sys_path):
return False
real_path = os.path.realpath(sys_path)
return "/usb" in real_path
def get_size_gb(dev):
name = os.path.basename(dev)
sectors_path = f"/sys/block/{name}/size"
with open(sectors_path, "r") as f:
sectors = int(f.read().strip())
return sectors * 512 / (1024 ** 3)
def wait_for_button_hold():
print("Waiting for button hold...", flush=True)
set_lights(yellow=True)
start = None
while True:
pressed = GPIO.input(PIN_BUTTON) == GPIO.LOW
if pressed:
if start is None:
start = time.time()
elapsed = time.time() - start
if int(elapsed * 4) % 2 == 0:
set_lights(red=True, yellow=True)
else:
set_lights(yellow=True)
if elapsed >= HOLD_SECONDS:
return
else:
start = None
set_lights(yellow=True)
time.sleep(0.05)
def unmount_partitions(dev):
result = run(["lsblk", "-ln", "-o", "NAME", dev], check=True)
for line in result.stdout.splitlines():
name = line.strip()
if not name:
continue
if name == os.path.basename(dev):
continue
part_path = "/dev/" + name
subprocess.run(
["umount", part_path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
def wait_for_partition(part):
for _ in range(20):
if os.path.exists(part):
return
time.sleep(0.5)
raise RuntimeError(f"Partition did not appear: {part}")
def copy_windows_identity_files(part):
if not os.path.exists(AUTORUN_SRC):
raise RuntimeError(f"Missing {AUTORUN_SRC}")
if not os.path.exists(ICON_SRC):
raise RuntimeError(f"Missing {ICON_SRC}")
os.makedirs(MOUNT_DIR, exist_ok=True)
subprocess.run(
["umount", MOUNT_DIR],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
run(["mount", "-t", "exfat", part, MOUNT_DIR])
try:
autorun_dst = os.path.join(MOUNT_DIR, "autorun.inf")
icon_dst = os.path.join(MOUNT_DIR, "favicon.ico")
shutil.copy2(AUTORUN_SRC, autorun_dst)
shutil.copy2(ICON_SRC, icon_dst)
# Optional Windows-compatible attributes on FAT/exFAT:
# +h = hidden, +s = system. Failures are ignored.
subprocess.run(["fatattr", "+h", autorun_dst], check=False)
subprocess.run(["fatattr", "+s", autorun_dst], check=False)
subprocess.run(["fatattr", "+h", icon_dst], check=False)
subprocess.run(["fatattr", "+s", icon_dst], check=False)
run(["sync"])
finally:
subprocess.run(
["umount", MOUNT_DIR],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
def format_exfat(dev):
print(f"Formatting {dev} as exFAT...", flush=True)
set_lights(red=True)
unmount_partitions(dev)
run(["wipefs", "-a", dev], check=False)
# Use a standard MBR/msdos partition table and one primary partition.
# Some parted versions do not accept "exfat" as a mkpart filesystem type,
# so the partition is created generically and formatted with mkfs.exfat.
run(["parted", "-s", dev, "mklabel", "msdos"])
run(["parted", "-s", dev, "mkpart", "primary", "1MiB", "100%"])
run(["partprobe", dev], check=False)
time.sleep(2)
part = dev + "1"
wait_for_partition(part)
run(["mkfs.exfat", "-n", FS_LABEL, part])
print("Copying autorun.inf and favicon.ico...", flush=True)
copy_windows_identity_files(part)
run(["sync"])
subprocess.run(
["udisksctl", "power-off", "-b", dev],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
def main():
if len(sys.argv) < 2:
raise RuntimeError("Usage: format_usb.py /dev/sdX")
signal.signal(signal.SIGTERM, cleanup)
signal.signal(signal.SIGINT, cleanup)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PIN_GREEN, GPIO.OUT)
GPIO.setup(PIN_YELLOW, GPIO.OUT)
GPIO.setup(PIN_RED, GPIO.OUT)
GPIO.setup(PIN_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
set_lights()
dev = get_device_from_arg(sys.argv[1])
print(f"Detected device: {dev}", flush=True)
if not os.path.exists(dev):
raise RuntimeError(f"Device does not exist: {dev}")
if not is_usb_device(dev):
raise RuntimeError(f"Refusing non-USB device: {dev}")
size_gb = get_size_gb(dev)
print(f"Device size: {size_gb:.1f} GB", flush=True)
if size_gb > MAX_SIZE_GB:
raise RuntimeError(f"Refusing device larger than {MAX_SIZE_GB} GB")
blink(yellow=True, times=2)
wait_for_button_hold()
blink(red=True, times=3)
format_exfat(dev)
set_lights(green=True)
print("Done. You may remove the USB drive.", flush=True)
time.sleep(10)
set_lights()
if __name__ == "__main__":
try:
main()
except Exception as e:
print(f"ERROR: {e}", flush=True)
try:
import traceback
traceback.print_exc()
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PIN_GREEN, GPIO.OUT)
GPIO.setup(PIN_YELLOW, GPIO.OUT)
GPIO.setup(PIN_RED, GPIO.OUT)
blink(red=True, times=10, delay=0.15)
set_lights(red=True)
time.sleep(10)
set_lights()
GPIO.cleanup()
except Exception:
pass
sys.exit(1)Save and set permissions:
sudo chmod +x /usr/local/bin/format_usb.py
python3 -m py_compile /usr/local/bin/format_usb.pyThis script monitors the same button. If no USB drive is present and the button is held for 5 seconds, it safely powers off the Raspberry Pi.
Create:
sudo nano /usr/local/bin/button_shutdown_monitor.pyPaste:
#!/usr/bin/env python3
import os
import time
import subprocess
import RPi.GPIO as GPIO
PIN_BUTTON = 16
HOLD_SECONDS = 5.0
POLL_INTERVAL = 0.05
def usb_disk_present():
block_dir = "/sys/block"
for name in os.listdir(block_dir):
if not name.startswith("sd"):
continue
# Accept only sda, sdb, ... and ignore partitions or unusual names.
if len(name) != 3:
continue
sys_path = os.path.join(block_dir, name)
real_path = os.path.realpath(sys_path)
if "/usb" in real_path:
return True
return False
def shutdown_now():
subprocess.run(["sync"], check=False)
subprocess.run(["systemctl", "poweroff"], check=False)
def main():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PIN_BUTTON, GPIO.IN, pull_up_down=GPIO.PUD_UP)
pressed_since = None
try:
while True:
pressed = GPIO.input(PIN_BUTTON) == GPIO.LOW
if pressed:
if pressed_since is None:
pressed_since = time.time()
held = time.time() - pressed_since
if held >= HOLD_SECONDS:
if not usb_disk_present():
shutdown_now()
time.sleep(10)
else:
# A USB drive is present, so the button belongs to the formatter.
# Wait for release to prevent shutdown after drive removal.
while GPIO.input(PIN_BUTTON) == GPIO.LOW:
time.sleep(POLL_INTERVAL)
pressed_since = None
else:
pressed_since = None
time.sleep(POLL_INTERVAL)
finally:
GPIO.cleanup()
if __name__ == "__main__":
main()Save and set permissions:
sudo chmod +x /usr/local/bin/button_shutdown_monitor.py
python3 -m py_compile /usr/local/bin/button_shutdown_monitor.pyCreate:
sudo nano /etc/systemd/system/usb-format@.servicePaste:
[Unit]
Description=USB formatter for %I
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/local/bin/format_usb.py /dev/%I
Restart=no
StandardOutput=journal
StandardError=journalCreate:
sudo nano /etc/udev/rules.d/99-usb-format.rulesPaste as one line:
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]", DEVTYPE=="disk", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-format@%k.service"
The Python script itself still verifies that the device is actually USB before doing anything destructive.
Create:
sudo nano /etc/systemd/system/button-shutdown-monitor.servicePaste:
[Unit]
Description=Button shutdown monitor
After=multi-user.target
[Service]
Type=simple
ExecStart=/usr/local/bin/button_shutdown_monitor.py
Restart=always
RestartSec=2
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetEnable everything:
sudo systemctl daemon-reload
sudo udevadm control --reload-rules
sudo systemctl enable button-shutdown-monitor.service
sudo systemctl start button-shutdown-monitor.serviceReboot:
sudo rebootInsert a disposable test flash drive.
Find its device name:
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,TRAN,MODELExample:
sda 28.7G disk usb SanDisk
└─sda1 28.7G part exfat OLDNAME
Run manually:
sudo /usr/local/bin/format_usb.py /dev/sdaExpected behavior:
Yellow LED -> waiting for button
hold button for 3 seconds
Red LED -> formatting
Green LED -> done
Verify result:
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,TRAN,MODELExpected:
sda1 ... exfat IPHYS CAS usb ...
After reboot, open a terminal and watch logs:
journalctl -fInsert a disposable USB flash drive. The formatter service should start automatically and wait for the button.
For a specific service instance, for example sda:
journalctl -u usb-format@sda.service --no-pager -n 100You can also test the service manually:
sudo systemctl start usb-format@sda.serviceIf you do not have SSH access to the Raspberry Pi, prepare a setup USB drive on Windows:
usb-formatter/
format_usb.py
button_shutdown_monitor.py
gpio_test.py
usb-format@.service
button-shutdown-monitor.service
99-usb-format.rules
autorun.inf
favicon.ico
On Raspberry Pi, copy files from the setup USB:
mkdir -p ~/usb-formatter-setup
cp /media/$USER/*/usb-formatter/* ~/usb-formatter-setup/Fix Windows CRLF line endings:
sed -i 's/\r$//' ~/usb-formatter-setup/*.py
sed -i 's/\r$//' ~/usb-formatter-setup/*.service
sed -i 's/\r$//' ~/usb-formatter-setup/*.rules
sed -i 's/\r$//' ~/usb-formatter-setup/autorun.infCopy into system locations:
sudo mkdir -p /opt/usb-formatter/assets
sudo cp ~/usb-formatter-setup/autorun.inf /opt/usb-formatter/assets/autorun.inf
sudo cp ~/usb-formatter-setup/favicon.ico /opt/usb-formatter/assets/favicon.ico
sudo cp ~/usb-formatter-setup/format_usb.py /usr/local/bin/format_usb.py
sudo cp ~/usb-formatter-setup/button_shutdown_monitor.py /usr/local/bin/button_shutdown_monitor.py
sudo cp ~/usb-formatter-setup/usb-format@.service /etc/systemd/system/usb-format@.service
sudo cp ~/usb-formatter-setup/button-shutdown-monitor.service /etc/systemd/system/button-shutdown-monitor.service
sudo cp ~/usb-formatter-setup/99-usb-format.rules /etc/udev/rules.d/99-usb-format.rules
cp ~/usb-formatter-setup/gpio_test.py ~/gpio_test.py
sudo chmod +x /usr/local/bin/format_usb.py
sudo chmod +x /usr/local/bin/button_shutdown_monitor.py
chmod +x ~/gpio_test.py
sudo systemctl daemon-reload
sudo udevadm control --reload-rules
sudo systemctl enable button-shutdown-monitor.service
sudo systemctl start button-shutdown-monitor.serviceHidden attributes for autorun.inf and favicon.ico
The formatting script uses:
subprocess.run(["fatattr", "+h", autorun_dst], check=False)
subprocess.run(["fatattr", "+s", autorun_dst], check=False)
subprocess.run(["fatattr", "+h", icon_dst], check=False)
subprocess.run(["fatattr", "+s", icon_dst], check=False)This marks the files as hidden and system files on FAT/exFAT. Install fatattr if available:
sudo apt install -y fatattrIf fatattr is not available, the script still formats the drive and copies the files.
If a script does not run, check permissions:
ls -l /usr/local/bin/format_usb.py
ls -l /usr/local/bin/button_shutdown_monitor.py
sudo chmod +x /usr/local/bin/format_usb.py
sudo chmod +x /usr/local/bin/button_shutdown_monitor.pypython3 -m py_compile /usr/local/bin/format_usb.py
python3 -m py_compile /usr/local/bin/button_shutdown_monitor.pyNo output means the Python syntax is valid.
The script refuses unusual device names and accepts only whole disks named like:
/dev/sda
/dev/sdb
/dev/sdc
It refuses names such as:
/dev/sda1 # partition, not whole disk
/dev/mmcblk0 # likely Raspberry Pi microSD system card
/dev/nvme0n1 # possible internal NVMe disk
/dev/loop0 # loop device
/dev/mapper/... # LUKS/LVM/RAID mapping
It also checks that the block device is actually on the USB bus.
The maximum size limit is controlled by:
MAX_SIZE_GB = 256This is a safety guard to reduce the chance of accidentally wiping a large external HDD/SSD. For presentation drives, consider lowering it to 64 or 128 if you only use small flash drives.
This section describes optional hardening steps for reducing the risk that a malicious or compromised USB device can affect the Raspberry Pi before the drive is wiped and reformatted.
These steps improve isolation, but they still do not make this device a 100% secure malware removal system. In particular, software hardening cannot fully protect against all malicious USB firmware, USB controller bugs, kernel USB stack vulnerabilities, or advanced BadUSB-style attacks.
Recommended baseline for higher-risk use:
Raspberry Pi OS Lite
no desktop automount
no unnecessary network access
USBGuard enabled
read-only root filesystem / overlay filesystem if practical
only one physically accessible USB port
Use Raspberry Pi OS Lite for the appliance if possible. Avoid desktop environments and file managers that may automatically inspect or mount USB media when inserted.
If a Desktop image was used, consider disabling or removing automount-related components before using the device with untrusted USB drives.
sudo systemctl disable --now udisks2.service || true
sudo apt purge -y gvfs* pcmanfm* || trueNote: udisks2 is used by the formatting script only for the final optional udisksctl power-off -b /dev/sdX step. If you remove udisks2, formatting still works, but the final USB power-off command may fail harmlessly depending on your script version.
USBGuard can be used to reject unexpected USB device classes such as keyboards, mice, network adapters, serial adapters, and composite devices. This is useful because some malicious USB devices do not behave like flash drives at all; they may emulate a keyboard or another device type.
Install and enable USBGuard:
sudo apt update
sudo apt install -y usbguard
sudo systemctl enable --now usbguardReview the configuration:
sudo nano /etc/usbguard/usbguard-daemon.confRecommended restrictive options:
ImplicitPolicyTarget=reject
PresentDevicePolicy=apply-policy
PresentControllerPolicy=keep
InsertedDevicePolicy=apply-policy
RestoreControllerDeviceState=falseThen create or edit the rules file:
sudo nano /etc/usbguard/rules.confExample restrictive policy:
# Allow USB mass storage devices.
allow with-interface equals { 08:*:* }
# Reject USB HID devices such as keyboards and mice.
reject with-interface equals { 03:*:* }
# Reject common wireless controller classes.
reject with-interface equals { e0:*:* }
# Reject everything else by default.
reject
Restart USBGuard after changing the configuration:
sudo systemctl restart usbguard
sudo systemctl status usbguard --no-pagerTest this carefully with disposable USB flash drives before routine use. Some flash drives or card readers may appear as composite devices; the policy may need local adjustment.
The Python formatter already checks that the target disk is on the USB bus before doing anything destructive. You can also make the udev rule more selective so the formatter service is requested only for USB block devices.
Replace:
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]", DEVTYPE=="disk", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-format@%k.service"
with:
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z]", DEVTYPE=="disk", ENV{ID_BUS}=="usb", TAG+="systemd", ENV{SYSTEMD_WANTS}+="usb-format@%k.service"
Reload udev and systemd:
sudo udevadm control --reload-rules
sudo systemctl daemon-reloadThe script mounts the USB partition only after wiping, repartitioning, and creating a new exFAT filesystem. As an additional safety measure, the temporary mount can use nosuid, nodev, and noexec.
In copy_windows_identity_files(), replace:
run(["mount", "-t", "exfat", part, MOUNT_DIR])with:
run(["mount", "-t", "exfat", "-o", "nosuid,nodev,noexec,noatime,umask=077", part, MOUNT_DIR])For highest isolation, run the appliance without network access during routine use.
sudo systemctl disable --now ssh || true
sudo systemctl disable --now bluetooth hciuart || true
sudo rfkill block wifi || true
sudo rfkill block bluetooth || trueIf SSH is needed for maintenance, enable it only temporarily or restrict it to a trusted management network.
For a dedicated appliance, consider enabling a read-only root filesystem or overlay filesystem. This makes persistent compromise of the Raspberry Pi harder and makes it easier to restore a known-good state by rebooting or reflashing the microSD card.
- Expose only the USB port intended for the drive being formatted.
- Physically block unused USB ports if the device is used by non-technical users.
- Do not connect the Raspberry Pi to trusted internal networks while processing untrusted USB devices.
- Treat the Raspberry Pi itself as semi-disposable in high-risk environments: keep a known-good image and be prepared to reflash the microSD card.
If the file was created in Windows, it may have incorrect \r line endings. In that case, this can help:
dos2unix /usr/local/bin/format_usb.pySome versions of parted do not accept exfat in the mkpart command. Use:
run(["parted", "-s", dev, "mkpart", "primary", "1MiB", "100%"])and then format with:
run(["mkfs.exfat", "-n", FS_LABEL, part])Check the service and rule:
systemctl cat usb-format@.service
cat /etc/udev/rules.d/99-usb-format.rules
sudo udevadm control --reload-rules
sudo systemctl daemon-reloadCheck device properties:
udevadm info --query=property --name=/dev/sda | grep -E 'ID_BUS|DEVNAME|DEVTYPE|SUBSYSTEM'This device reduces a specific risk: reusing a presentation USB drive after it has been inserted into an untrusted computer.
It does not guarantee full malware protection.
It does not protect against:
- malicious USB firmware / BadUSB-style devices;
- keyboard-emulating USB attacks;
- malware that targets Linux/exFAT tooling;
Recommended operational practice:
- Never plug post-event presentation drives directly into your main workstation.
- Reformat them first using this appliance.
- Copy clean presentation files back from a trusted source.
- Consider using read-only media or write-protected devices for higher-risk environments.
USB FORMATTER
No USB inserted:
hold 5 s = safe shutdown
USB inserted:
yellow = waiting
hold 3 s = format USB
red = formatting, do not remove
green = done
WARNING: ERASES THE WHOLE USB DRIVE
DO NOT INSERT EXTERNAL HDD/SSD
This project is released under the MIT License. See LICENSE for details. The hardware build and scripts are provided as-is, without any warranty. Use at your own risk.


