# Mercury: Unauthenticated Remote Code Execution via /api/kernels

Mercury's anonymous-by-default JupyterLab server blocklists dangerous endpoints but omits /api/kernels, letting unauthenticated clients create a kernel and execute arbitrary Python.

- Payout: Research
- Severity: critical
- Program: Mercury
- Found by: CyberKareem
- Disclosed: 2026-10-19
- CVE: CVE-2026-111058
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-rv4m-xm3f-3ccq
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-rv4m-xm3f-3ccq
- Tags: area:web domain:oss domain:web impact:rce kind:research platform:ghsa sev:critical tech:python technique:incomplete-fix technique:source-review vuln:access-control vuln:auth-bypass vuln:misconfiguration vuln:rce

**Target:** JupyterLab server kernel REST API and websocket channels
**Root cause:** Mercury runs JupyterLab tokenless by default and relies on a blocklist of dangerous endpoints; /api/kernels is absent from BLOCKED_PATTERNS and the kernel websocket handler wrapper only tracks idle timeout without filtering execute_request.
**Steps:**
1. Start mercury 3.2.4 with default tokenless config (no --token/MERCURY_TOKEN).
2. `POST /api/kernels {"name":"python3"}` -> 201 with kernel id.
3. Open websocket `/api/kernels/<id>/channels`.
4. Send an `execute_request` message with arbitrary Python code.
5. Code runs as the server process user (e.g. `whoami`, write `/tmp/PWNED_MERCURY_RCE`).
**How found:** Reviewed the blocklist in block_handler.py and noticed /api/kernels was missing; confirmed patch_kernel_websocket_handler only wraps on_message for idle-timeout tracking with no execute_request filtering, then validated at runtime on the version that fixed the /api/terminals advisory.
**Exploitation:** Unauthenticated network client creates a kernel via the REST API, connects to its channels websocket, and issues execute_request messages; no auth or XSRF check applies (disable_check_xsrf=True when tokenless), yielding arbitrary code execution as the server user.
**Insight:** When a product secures an embedded JupyterLab with a path blocklist, enumerate every kernel/terminal/session endpoint and the websocket channels handler; the blocklist can never cover the kernel channel the product itself needs, so the real fix is authentication, not more entries.

