{"id":"CVE-2026-111058","kind":"vulnerability","aliases":["CVE-2026-111058","GHSA-rv4m-xm3f-3ccq"],"related":[],"title":"Mercury: Unauthenticated Remote Code Execution via /api/kernels","description":"Mercury runs a JupyterLab server with **anonymous access by default** (`mercury_app/__main__.py` sets `--IdentityProvider.token=''` when no `--token`/`MERCURY_TOKEN` is given; when tokenless it also sets `--ServerApp.disable_check_xsrf=True`). It relies on a **blocklist** (`mercury_app/block_handler.py` `BLOCKED_PATTERNS`: `/lab`, `/api/terminals`, `/api/shutdown`, `/files`) to keep anonymous users off dangerous Jupyter endpoints.\n\n## The vulnerability\n`/api/kernels` is **NOT** in the blocklist (and cannot be — Mercury needs kernels to run notebooks), and `patch_kernel_websocket_handler()` (`mercury_app/idle_timeout.py`) only tracks the idle-timeout: it wraps `on_message` to call `self.application._timeout_manager.touch()` and then invokes the original handler with **no filtering of `execute_request`**. So any unauthenticated network client can:\n\n```\nPOST /api/kernels {\"name\":\"python3\"}        -> 201, returns kernel id\nopen websocket /api/kernels/<id>/channels\nsend an execute_request with arbitrary code -> arbitrary Python execution = RCE\n```\n\n## Distinct from GHSA-f8c7-rwf9-fmgm (incomplete fix)\nGHSA-f8c7 was \"Unauthenticated RCE via `/api/terminals`\" — a missing-comma blocklist bypass. Its fix (add the comma so `/api/terminals` is blocked) does **NOT** address `/api/kernels`, which remains exploitable. The blocklist approach is fundamentally unable to cover kernels, so this is not a variant that another blocklist entry fixes.\n\n## Runtime-proven (on the patched version)\nOn **mercury 3.2.4** — the release that fixed GHSA-f8c7 — with the default (tokenless) config, as an unauthenticated client: `POST /api/terminals` returns **403** (the f8c7 fix is present), but `POST /api/kernels` returns **201**; opening the kernel websocket and sending an `execute_request` executed attacker-supplied Python (ran `whoami` as the server process user and wrote a marker file `/tmp/PWNED_MERCURY_RCE`). Full unauthenticated remote code execution. Deterministic PoC (client script + notebook) included.\n\n## Impact\nUnauthenticated, network-reachable arbitrary code execution as the Mercury server process user on every default deployment — complete compromise of the host/container and any data/secrets it can reach. \n\n## Fix\nThe blocklist cannot secure an anonymous kernel-backed server. Require real per-request authentication (a token or password) by default, or restrict the kernel message channel so anonymous websocket clients cannot issue `execute_request` outside Mercury's own controlled execution flow.\n\n## Dedup (3-method)\nOSV.dev CLEAN(0) for `mercury` and `mljar-mercury`; the repo's only advisory (GHSA-f8c7-rwf9-fmgm) covers `/api/terminals` only (description does not mention kernels); no issue/PR describes the `/api/kernels` RCE (#219/#134/#262 are \"add auth\" feature requests that merely confirm anonymous-by-default). Sink independently confirmed on current HEAD (blocklist omits `/api/kernels`; `token=''` default; `patch_kernel_websocket_handler` does no execute filtering).\n\nFinder: Abdullah Kareem (GitHub: CyberKareem).","summary":"Mercury (the mljar Python package) runs a JupyterLab server that is anonymous by default and blocks only a few endpoints, leaving /api/kernels reachable. An unauthenticated network client can start a kernel, send an execute_request over websocket, and run arbitrary Python as the server process user.","severity":"critical","cvss":9.800000190734863,"epss":0.004370000213384628,"exploited":false,"exploited_at":null,"products":[{"name":"mercury","fixed":["3.2.6"],"affected":"<= 3.2.5","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-862","jupyter","mercury","package","python","self-hosted"],"not_affected_if":["auth-token-set","not-network-exposed","version-3.2.6-or-later"],"how_to_check":"Check the installed version with pip show mercury; versions 3.2.5 and below are affected. Look for mercury being started without --token or MERCURY_TOKEN set; that is the default-config case.","fix":"Upgrade mercury to 3.2.6 or later, per the advisory's fixed version.\nIf you cannot upgrade, set a token (--token or MERCURY_TOKEN) so anonymous access is disabled, and keep the server off untrusted networks.","status":"open","published_at":"2026-10-19T16:17:03+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T19:50:58+00:00","updated_at":"2026-10-23T21:57:18+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-10-23T19:50:58Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade mercury to 3.2.6 or later, per the advisory's fixed version.\nIf you cannot upgrade, set a token (--token or MERCURY_TOKEN) so anonymous access is disabled, and keep the server off untrusted networks.","summary":"Mercury (the mljar Python package) runs a JupyterLab server that is anonymous by default and blocks only a few endpoints, leaving /api/kernels reachable. An unauthenticated network client can start a kernel, send an execute_request over websocket, and run arbitrary Python as the server process user.","category":"package","who_cares":["python","mercury","self-hosted","jupyter"],"confidence":0.85,"analyst_note":"Confirm the 3.2.6 fix actually requires auth or filters execute_request, and whether a token-set deployment is fully protected, at https://github.com/advisories/GHSA-rv4m-xm3f-3ccq.","how_to_check":"Check the installed version with pip show mercury; versions 3.2.5 and below are affected. Look for mercury being started without --token or MERCURY_TOKEN set; that is the default-config case.\nunknown","needs_analyst":true,"not_affected_if":["version-3.2.6-or-later","auth-token-set","not-network-exposed"],"severity_if_unknown":"critical"},"cost_usd":0.000548,"input_tokens":3201,"output_tokens":455,"severity_before":"critical"},"analyst_status":null,"analyst_note":null,"search":"'-111058':10A,18A '-2026':9A,17A '-862':21B '/api/kernels':7A,50B,128C,183C,192C,240C,301C,449C,474C '/api/shutdown':116C '/api/terminals':115C,221C,234C,291C,438C '/channels':193C '/files':117C '/lab':114C '/tmp/pwned_mercury_rce':330C '0':420C '134':452C '201':186C,303C '219':451C '262':453C '3':416C '3.2.4':273C '3ccq':15A '403':293C 'abdullah':486C 'access':82C 'add':230C,455C 'address':239C 'advisori':431C 'also':99C 'anonym':40B,81C,120C,377C,401C,463C 'anonymous-by-default':462C 'anoth':260C 'app/__main__.py':86C 'app/block_handler.py':111C 'app/idle_timeout.py':149C 'approach':246C 'arbitrari':68B,199C,201C,347C 'attack':315C 'attacker-suppli':314C 'auth':456C 'authent':387C 'back':380C 'block':44B,112C,236C 'blocklist':109C,133C,226C,245C,261C,373C,472C 'bypass':227C 'call':161C 'cannot':135C,374C,404C 'channel':399C 'check':102C 'clean':419C 'client':55B,180C,289C,338C,403C 'code':4A,200C,334C,348C 'comma':225C,232C 'complet':360C 'compromis':361C 'config':285C 'confirm':461C,468C 'control':412C 'cover':251C,437C 'current':470C 'cve':8A,16A 'cwe':20B 'cyberkareem':489C 'danger':123C 'data/secrets':367C 'dedup':415C 'default':42B,84C,283C,358C,393C,465C,476C 'deploy':359C 'describ':447C 'descript':440C 'determinist':336C 'distinct':205C 'endpoint':48B,125C 'entri':262C 'everi':357C 'execut':5A,62B,174C,196C,203C,311C,313C,335C,349C,406C,413C,483C 'exploit':243C 'f8c7':209C,216C,280C,295C,434C 'featur':457C 'file':329C 'filter':172C,484C 'finder':485C 'fix':213C,229C,263C,277C,296C,371C 'flow':414C 'fmgm':211C,436C 'full':331C 'fundament':248C 'ghsa':12A,208C,215C,279C,433C 'ghsa-f8c7':214C,278C 'ghsa-f8c7-rwf9-fmgm':207C,432C 'ghsa-rv4m-xm3f-3ccq':11A 'github':488C 'given':95C 'handler':147C,169C,480C 'head':471C 'host':28B 'host/container':364C 'id':189C 'identityprovider.token':88C 'idl':154C 'idle-timeout':153C 'impact':342C 'includ':341C 'incomplet':212C 'independ':467C 'invok':166C 'issu':405C 'issue/pr':446C 'jupyt':22B,124C 'jupyterlab':36B,78C 'kareem':487C 'keep':119C 'kernel':59B,139C,145C,188C,252C,306C,379C,397C,444C,478C 'kernel-back':378C 'leav':49B 'marker':328C 'mention':443C 'mercuri':1A,23B,29B,75C,85C,92C,110C,137C,148C,272C,352C,409C,422C,426C 'mercury':19A 'mere':460C 'messag':159C,398C 'method':417C 'miss':224C 'missing-comma':223C 'mljar':31B,425C 'mljar-mercuri':424C 'name':184C 'need':138C 'network':54B,179C,345C 'network-reach':344C 'notebook':142C,340C 'omit':473C 'open':190C,304C 'origin':168C 'osv.dev':418C 'outsid':408C 'packag':24B,33B 'password':391C 'patch':144C,269C,477C 'pattern':113C 'per':385C 'per-request':384C 'poc':337C 'post':182C,290C,300C 'present':298C 'process':73B,323C,354C 'proven':266C 'python':25B,32B,69B,202C,317C 'python3':185C 'ran':318C 'rce':204C,219C,450C 'reach':370C 'reachabl':51B,346C 'real':383C 'releas':275C 'reli':106C 'remain':242C 'remot':3A,333C 'repo':428C 'request':63B,175C,197C,312C,386C,407C,458C 'requir':382C 'restrict':395C 'return':187C,292C,302C 'run':34B,67B,76C,141C 'runtim':265C 'runtime-proven':264C 'rv4m':13A 'rwf9':210C,435C 'script':339C 'secur':375C 'self':27B 'self-host':26B 'self.application':162C 'send':60B,194C,309C 'server':37B,72B,79C,322C,353C,381C 'serverapp.disable':101C 'set':87C,100C 'sink':466C 'start':57B 'suppli':316C 'timeout':155C 'timeout_manager.touch':163C 'token':91C,93C,389C,475C 'tokenless':97C,284C 'track':151C 'true':104C 'unabl':249C 'unauthent':2A,53B,178C,218C,288C,332C,343C 'user':74B,121C,324C,355C 'variant':258C 'version':270C 'via':6A,220C 'vulner':127C 'websocket':65B,146C,191C,307C,402C,479C 'whoami':319C 'wrap':157C 'wrote':326C 'xm3f':14A 'xsrf':103C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-19T16:17:03+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-rv4m-xm3f-3ccq","url":"https://github.com/advisories/GHSA-rv4m-xm3f-3ccq","severity":"critical","published_at":"2026-10-19T16:49:08+00:00","modified_at":"2026-10-19T16:49:08+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-111058","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-111058","severity":null,"published_at":"2026-10-19T16:17:03+00:00","modified_at":"2026-10-19T21:13:39+00:00","first_seen_at":"2026-10-23T21:57:18+00:00","fetched_at":"2026-10-23T22:28:57+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}