# Mercury: Unauthenticated Remote Code Execution in /api/terminals - access control bypass

Missing comma merged two regex blocklist entries in Mercury, leaving /api/terminals unblocked so unauthenticated attackers spawn a root OS shell via Jupyter's terminal API.

- Payout: Research
- Severity: critical
- Program: Mercury
- Found by: szotgan
- Disclosed: 2026-10-19
- CVE: CVE-2026-111057
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-f8c7-rwf9-fmgm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-f8c7-rwf9-fmgm
- Tags: area:web domain:oss domain:web impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:python technique:source-review vuln:access-control vuln:auth-bypass vuln:rce

**Target:** /api/terminals endpoint on Jupyter Server behind Mercury
**Root cause:** Adjacent string literals in BLOCKED_PATTERNS concatenate because a comma is missing, so the /api/terminals regex becomes a suffix of the /lab rule and never matches a direct request.
**Steps:**
1. Read `mercury_app/block_handler.py` and note the missing comma between the `/lab...` and `/api/terminals` regex strings
2. Send `curl -i -X POST http://localhost:8888/api/terminals` and observe `{"name": "1", ...}`
3. Open `ws://localhost:8888/terminals/websocket/1` and send `["stdin", "whoami && id\n"]`
4. Receive stdout showing `root` / `uid=0(root)`
**How found:** Code review of the blocklist regex list; noticing the missing comma and reasoning that Python implicit string concatenation would merge the /lab and /api/terminals patterns, then testing a direct request to the endpoint.
**Exploitation:** Unauthenticated POST to /api/terminals spawns a Jupyter terminal; connecting to the returned terminal's WebSocket and sending stdin frames yields an interactive root shell on the host, enabling file read/write, secret theft, DoS and lateral movement.
**Escalation:** Shell runs as root in the container, providing a foothold to pivot to other hosts reachable from the server's network.
**Insight:** When a denylist is built from adjacent regex string literals, verify each entry is a separate list element — implicit concatenation silently fuses rules and can leave a sensitive endpoint like Jupyter's /api/terminals reachable.

