Human
Machine
REPRO-2026-00140 CRITICAL RCE
Verified
zenshin: OS command injection in /stream-to-vlc url query parameter
May 22, 2026
What's the vulnerability?
zenshin is an Electron application with an Express HTTP backend. The backend
route /stream-to-vlc passes the url query parameter into a shell
command without sanitization. A crafted url value containing shell
metacharacters injects and executes arbitrary shell commands on the host —
remote code execution.
Any actor able to reach the local Express backend port can issue the request, unauthenticated.
Root Cause Analysis
# RCA Report — CVE-2026-37281
## Summary
`zenshin` is an Electron application with an embedded Express HTTP backend. The backend route `GET /stream-to-vlc` takes a `url` query parameter and interpolates it unsanitized into a shell command executed via `child_process.exec`. A crafted `url` value containing shell metacharacters (e.g., a closing quote followed by a command separator) allows arbitrary command execution on the host, resulting in unauthenticated remote code execution for any actor that can reach the backend port.
## Impact
- **Package/component affected**: `zenshin` (Electron/Express backend)
- **Affected versions**: `< 2.7.0`
- **Fixed version**: `2.7.0` (commit `7d31c6edfbac978f0ad44c66d761bab9dcd2fa27`)
- **Risk level**: Critical — CVSS 3.1 base 9.8
- **Consequences**: Unauthenticated attackers on the local network (or any host that can reach the Express port) can execute arbitrary OS commands as the user running zenshin.
## Root Cause
In the vulnerable commit (`7d31c6edfbac978f0ad44c66d761bab9dcd2fa27~1`), the `/stream-to-vlc` route in `Electron/zenshin-electron/src/main/index.js` constructs a shell command by directly interpolating the user-supplied `url` query parameter:
```javascript
const vlcPath = '"C:\\Program Files (x86)\\VideoLAN\\VLC\\vlc.exe"'
const vlcCommand = `${vlcPath} "${url}"`
exec(vlcCommand, (error) => { ... })
```
Because `exec` invokes `/bin/sh -c` (or `cmd.exe /c` on Windows), any shell metacharacters in `url` are interpreted by the shell. An attacker can break out of the double-quote wrapper with a payload such as `x"; touch /tmp/pwned; echo "x`, causing the shell to execute the injected `touch /tmp/pwned` command after the (non-existent) VLC executable fails.
The fix commit (`7d31c6edfbac978f0ad44c66d761bab9dcd2fa27`) completely removes the `/stream-to-vlc` Express endpoint and replaces the `ipcMain` handler with `spawn`, passing the player path and URL as separate array arguments with `shell: false`, which prevents shell interpretation entirely.
## Reproduction Steps
The reproduction script is `repro/reproduction_steps.sh`. It performs the following steps:
1. Checks out the vulnerable commit (`7d31c6edfbac978f0ad44c66d761bab9dcd2fa27~1`).
2. Installs dependencies and builds the project with `npm run build` inside `Electron/zenshin-electron`.
3. Runs the actual built `out/main/index.js` with a Node.js mock harness that stubs `electron`, `@electron-toolkit/utils`, and `electron-deeplink`, allowing the Express backend to start without a real Electron runtime.
4. Sends a crafted `GET /stream-to-vlc?url=...` request with the payload `x"; touch /tmp/pwned; echo "x`.
5. Verifies that `/tmp/pwned` is created, proving arbitrary command execution.
6. Repeats steps 1–5 with the fixed commit (`7d31c6edfbac978f0ad44c66d761bab9dcd2fa27`).
7. Verifies that the endpoint returns `404 Cannot GET /stream-to-vlc` and `/tmp/pwned` is NOT created.
## Evidence
- **Vulnerable commit result**: `statusCode=200`, `responseBody="VLC launched successfully"`, `pwned_exists=true`
- **Fixed commit result**: `statusCode=404`, `responseBody="Cannot GET /stream-to-vlc"`, `pwned_exists=false`
- **Log files**:
- `logs/vulnerable.json` — HTTP response and sentinel file state for the vulnerable build
- `logs/fixed.json` — HTTP response and sentinel file state for the fixed build
- `logs/run.log` — full build and execution logs
- **Runtime manifest**: `repro/runtime_manifest.json` captures the structured verdict.
## Recommendations / Next Steps
- **Fix approach**: Remove or replace `exec` with `spawn` using an array of arguments and `shell: false`, as the upstream fix does. This prevents the shell from parsing user input.
- **Input validation**: If the endpoint must be retained, strictly validate the `url` parameter against an allow-list of safe stream URL patterns before passing it to any process launcher.
- **Network binding**: Bind the Express backend to `127.0.0.1` only (also done in the fix) to reduce the attack surface.
- **Testing**: Add integration tests that attempt shell injection against `/stream-to-vlc` and similar endpoints to prevent regressions.
## Additional Notes
- **Idempotency**: The reproduction script was run twice consecutively with identical results on both runs.
- **Edge cases / limitations**: The reproduction uses a mock Electron environment because a real Electron GUI cannot run in a headless CI/container environment. The mock stubs only Electron-specific APIs (window creation, IPC, deeplinks); the Express backend and the vulnerable route handler are the actual transpiled code from the project build.
One Command
Verify with pruva-verify
Run the Pruva CLI to automatically fetch and execute the reproduction script.
pruva-verify REPRO-2026-00140 or
pruva-verify CVE-2026-37281 Install:
curl -fsSL https://pruva.dev/install.sh | sh Or Run Manually
1
Download the script
curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00140/artifacts/reproduction_steps.sh 2
Make executable
chmod +x reproduction_steps.sh 3
Run the script
./reproduction_steps.sh Run in a VM, container, or disposable environment. This exploits a real vulnerability.
How Pruva Reproduced This
Watch the AI agent's step-by-step process.
Loading session...
Artifacts
No artifacts available