The Reachy Mini Bluetooth command handler is vulnerable to Arbitrary Root Script Execution via Path Traversal
reachy-mini (Bluetooth command service)
< 1.10.0
The Reachy Mini Bluetooth command handler (BluetoothCommandService._handle_command in src/reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py) accepts a CMD_ payload after a successful PIN authentication and runs the matching .sh file from the commands/ directory with sudo. The script name is taken from the BLE payload with no sanitization and combined using os.path.join("commands", script_name). On POSIX, if the second argument is an absolute path, os.path.join discards the commands/ prefix entirely, so a payload such as CMD_/tmp/reachy_pwn causes the handler to execute sudo /tmp/reachy_pwn.sh. Relative payloads such as CMD_../attacker/pwn likewise escape the intended directory. An attacker who can place a .sh file on the filesystem, for example via the unrestricted media-sounds upload (CVE-2026-55419), can therefore run that script as root.
This issue is the third step in a documented compromise chain: unrestricted file upload, Bluetooth authentication bypass, then this directory traversal. The published CVSS score (7.2, AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H) reflects authenticated Bluetooth access.
The vulnerable join looks like this:
script_name = command_str[4:].strip() + ".sh"
script_path = os.path.join("commands", script_name)
if os.path.isfile(script_path):
subprocess.run(["sudo", script_path], capture_output=True, text=True)
Python documents that an absolute second argument replaces the earlier join pieces, so os.path.join("commands", "/tmp/reachy_pwn.sh") evaluates to /tmp/reachy_pwn.sh.
Step 1 - Confirm the os.path.join bypass
python3 -c "import os; print(os.path.join('commands', '/tmp/reachy_pwn.sh')); print(os.path.join('commands', '../attacker/pwn.sh'))"
Expected output:
/tmp/reachy_pwn.sh
../attacker/pwn.sh
The first argument is discarded when the second argument is an absolute path. A ../ segment also leaves commands/.
Step 2 - Authenticate on the Bluetooth command characteristic
From a BLE client (nRF Connect, Web Bluetooth, or the Reachy Mini control app), write the robot PIN. The handler expects the PIN_ prefix followed by the last five digits of the serial, for example:
PIN_12345
Expected response:
OK: Connected
Step 3 - Send an absolute CMD_ payload
The handler appends .sh to whatever follows CMD_. After authentication, write:
CMD_/tmp/reachy_pwn
On a vulnerable build this resolves to sudo /tmp/reachy_pwn.sh instead of a file under commands/. The same escape works with a relative payload such as CMD_../attacker/pwn.
Step 4 - Confirm with the vendor regression test
The vendor shipped tests/unit_tests/test_ble_path_traversal.py with the fix. On a Linux checkout of an unpatched tree, copy that test in and run:
pytest tests/unit_tests/test_ble_path_traversal.py -v
Expected output on a vulnerable build:
PATH TRAVERSAL: handler invoked `sudo ...` for payload ...
FAILED tests/unit_tests/test_ble_path_traversal.py
Those tests assert that the handler never invokes sudo on a path outside commands/. They fail while the vulnerability is present and pass once the command name is constrained to a bare [A-Za-z0-9_-]+ filename.
Upgrade to reachy-mini 1.10.0 or later. The fix takes only the final slash-separated component of the client-supplied command name and allow-lists [A-Za-z0-9_-] before joining it into commands/ (https://github.com/pollen-robotics/reachy_mini/commit/caed7d88cd5190c8789cfa4f7d8e34c48ccb14d9). Until then, restrict Bluetooth pairing to trusted devices and do not leave attacker-writable .sh files on the robot filesystem.
https://github.com/pollen-robotics/reachy_mini/security/advisories/GHSA-vg3x-4hxm-3gxm https://github.com/pollen-robotics/reachy_mini/commit/caed7d88cd5190c8789cfa4f7d8e34c48ccb14d9 https://github.com/pollen-robotics/reachy_mini/releases/tag/v1.10.0 https://nvd.nist.gov/vuln/detail/CVE-2026-62661