Mercury: Unauthenticated Remote Code Execution in /api/terminals - access control bypass
### 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
- Products
- mercury
- Affected versions
- 3.0.0, 3.0.1, 3.0.2, 3.1.0, 3.1.1, 3.1.2, 3.1.4, 3.1.5, 3.2.0, 3.2.1, 3.2.2, 3.2.3
- Fixed versions
- 3.2.4
- CVEs
- CVE-2026-111057
- Confirmed fixed CVEs
- Not specified
- Updated in this feed
- 31 Oct 2026, 08:21 UTC · Revision 1
Source evidence
Mercury: Unauthenticated Remote Code Execution in /api/terminals - access control bypass
core-osv · Original sourcePublished 19 Oct 2026, 17:00 UTC
### 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
osv:GHSA-f8c7-rwf9-fmgm
mercury
Fixed versions: 3.2.4
Complete source record
{
"id": "0d489217c5256704b8125623737ef391b2e65dae92494613388f3641519433ae",
"url": "https://osv.dev/vulnerability/GHSA-f8c7-rwf9-fmgm",
"method": "structured",
"payload": {
"kind": "advisory",
"title": "Mercury: Unauthenticated Remote Code Execution in /api/terminals - access control bypass",
"summary": "### 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",
"evidence": "osv:GHSA-f8c7-rwf9-fmgm",
"products": [
{
"name": {
"value": "mercury",
"evidence": "/affected/0/package/name"
},
"prerequisites": [],
"fixed_versions": [
{
"value": "3.2.4",
"evidence": "/affected/0/ranges"
}
],
"affected_versions": [
{
"value": "3.0.0",
"evidence": "/affected/0/versions"
},
{
"value": "3.0.1",
"evidence": "/affected/0/versions"
},
{
"value": "3.0.2",
"evidence": "/affected/0/versions"
},
{
"value": "3.1.0",
"evidence": "/affected/0/versions"
},
{
"value": "3.1.1",
"evidence": "/affected/0/versions"
},
{
"value": "3.1.2",
"evidence": "/affected/0/versions"
},
{
"value": "3.1.4",
"evidence": "/affected/0/versions"
},
{
"value": "3.1.5",
"evidence": "/affected/0/versions"
},
{
"value": "3.2.0",
"evidence": "/affected/0/versions"
},
{
"value": "3.2.1",
"evidence": "/affected/0/versions"
},
{
"value": "3.2.2",
"evidence": "/affected/0/versions"
},
{
"value": "3.2.3",
"evidence": "/affected/0/versions"
}
]
}
],
"severity": "critical",
"structured": {
"id": "GHSA-f8c7-rwf9-fmgm",
"aliases": [
"CVE-2026-111057",
"GHSA-f8c7-rwf9-fmgm"
],
"metrics": [
{
"type": "CVSS_V3",
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
}
],
"affected": [
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "3.2.4"
}
]
}
],
"package": {
"name": "mercury",
"purl": "pkg:pypi/mercury",
"ecosystem": "PyPI"
},
"versions": [
"3.0.0",
"3.0.1",
"3.0.2",
"3.1.0",
"3.1.1",
"3.1.2",
"3.1.4",
"3.1.5",
"3.2.0",
"3.2.1",
"3.2.2",
"3.2.3"
],
"database_specific": {
"source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-f8c7-rwf9-fmgm/GHSA-f8c7-rwf9-fmgm.json",
"last_known_affected_version_range": "<= 3.2.3"
}
}
],
"modified": "2026-10-19T17:15:04.105175540Z",
"provider": "osv",
"references": [
"https://github.com/mljar/mercury/security/advisories/GHSA-f8c7-rwf9-fmgm",
"https://github.com/mljar/mercury/commit/8c467129037ee9df269d543ac217ffd33b97bf3f",
"https://github.com/mljar/mercury",
"https://github.com/mljar/mercury/releases/tag/v3.2.4"
],
"provider_metadata": {
"cwe_ids": [
"CWE-185",
"CWE-284"
],
"severity": "CRITICAL",
"github_reviewed": true,
"nvd_published_at": null,
"github_reviewed_at": "2026-10-19T17:00:54Z"
}
},
"related_cves": [
{
"value": "CVE-2026-111057",
"evidence": "/aliases"
}
],
"security_relevant": true,
"severity_evidence": "/database_specific/severity"
},
"source_id": "core-osv",
"document_id": 375519,
"published_at": "2026-10-19T17:00:54+00:00"
}