VDB
Sign up
MEDIUM5.3

PYSEC-2026-3916

reachy_mini Allows Unrestricted Upload of File with Dangerous Type

Quick fix

PYSEC-2026-3916 — reachy-mini: upgrade to the fixed version with the command below.

pip install --upgrade 'reachy-mini>=1.8.2'

Details

## Summary

The Reachy Mini daemon exposes the “/api/media/sounds/upload” endpoint without authentication and file validation mechanisms. An attacker can use this endpoint to upload malicious files into the file system that will propagate in future attacks.

## Compromise Chain: Unauthenticated to Full Root Access

This issue is part of a full compromise chain allowing an unauthenticated user to gain root access on the Reachy’s operating system:

1. Unrestricted File Upload in Media Sounds Upload API \<= current finding 2. Bluetooth Authentication Bypass 3. Bluetooth Directory Traversal

## Description

The root cause of the issue is at the handler located in “***src/daemon/app/routers/media.py***” file at the “upload\_sound” method:

```py @router.post("/sounds/upload") async def upload_sound( file: UploadFile = File(...), ) -> dict[str, str]: """Upload a sound file to the daemon's temporary sound directory. The file is saved to ``/tmp/reachy_mini_sounds/<original_filename>``. If a file with the same name already exists it is overwritten. Returns: JSON with the absolute *path* of the saved file on the daemon. """ if not file.filename: raise HTTPException(status_code=400, detail="Filename is required") # Reject path traversal filename = Path(file.filename).name if not filename or filename in (".", ".."): raise HTTPException(status_code=400, detail="Invalid filename") os.makedirs(SOUNDS_TMP_DIR, exist_ok=True) dest = os.path.join(SOUNDS_TMP_DIR, filename) content = await file.read() with open(dest, "wb") as f: f.write(content) return {"status": "ok", "path": dest} ```

This endpoint lacks multiple defence mechanisms:

1. No authentication mechanism. 2. No file extension validation. 3. No file content/size validation.

Additionally, the daemon is bound to the 0.0.0.0 network interfaces (a.k.a. all network interfaces) by default along with permissive CORS ( allow\_origins=\[“\*”\] ) meaning the following API endpoint is exposed to every network interface the daemon is connected to.

# PoC

1. Start the daemon in simulation mode (command depends on the installed environment):

```shell .venv/bin/mjpython -m reachy_mini.daemon.app.main --sim --no-media ```

2. After that check that the media upload API endpoint is activated and you can upload a wav file:

```shell curl -X POST http://<daemon_domain>:<daemon_port>/api/media/sounds/upload \ -F "file=@/path/to/your/file.wav" ```

3. Now attempt to create a “.sh” file containing a script and upload it:

```shell curl -X POST http://<daemon_domain>:<daemon_port>/api/media/sounds/upload \ -F "file=@/path/to/your/script.sh" ```

4. Now error message will be received and you will see that the script file was successfully uploaded to disk.

# Impact

Due to this issue, an attacker can upload malicious files instead of the intended sounds files, harming the integrity of the stored data and allowing an attacker to propagate a foothold in cases another vulnerabilities would arise.

## Fix suggestion

Perform the following check on the API endpoint:

1. Validate that the file extension contains only desired extensions (allow-list approach). 2. Validate that the uploaded file’s content matches the desired extension (Magic numbers, and known file structure per file type). 3. Enforce authentication on the file upload endpoint.

## Credit

The vulnerability was discovered by Natan Nehorai of the JFrog Vulnerability Research team.

Are you affected?

Enter the version of the package you're using.

Affected packages

PyPI/reachy-mini
Introduced in: 0Fixed in: 1.8.2
Fixpip install --upgrade 'reachy-mini>=1.8.2'

References