The Planner Was the Shell: CVE-2026-105135 Puts exec() Behind an Unauthenticated Port on 6,900 Stars of Abandoned Code

CVE-2026-105135 reached the National Vulnerability Database at 07:16 UTC on 4 October 2026, assigned by VulDB. The record is terse: in InternLM MindSearch 0.1.0, the function ExecutionAction.run in mindsearch/agent/graph.py — the Planner Agent — takes a manipulated inputs argument and performs code injection, remotely, with no privileges. VulDB scores it CVSS 4.0 base 9.3 and CVSS 3.1 base 10.0, weaknesses CWE-74 and CWE-94, exploit maturity proof-of-concept. The last line of the NVD description is the one that decides what to do about it: “The vendor was contacted early about this disclosure but did not respond in any way.”

We read the source rather than the summary. The defect is not subtle, and it is still in main at the time of writing.

Three lines, in the order they matter

MindSearch is a multi-agent web-search framework from Shanghai AI Laboratory / InternLM — an open reimplementation of the Perplexity-style “plan, search, synthesise” loop. Its architecture makes the planner write Python that builds a WebSearchGraph, and then runs it. Reading the current repository (7952c5f):

  • mindsearch/agent/mindsearch_agent.py sets up the execution namespace as local_dict, global_dict = {}, globals() — the process’s own globals, with full __builtins__, not a restricted dictionary.
  • mindsearch/agent/graph.py defines ExecutionAction.run, whose entire input filter is extract_code(): strip any from … import WebSearchGraph line, then return the contents of the first triple-backtick fence found anywhere in the model message. The next statement is exec(command, global_dict, local_dict). No AST allowlist, no subprocess, no sandbox, no timeout.
  • mindsearch/app.py registers app.add_api_route("/solve", …, methods=["POST"]) with no authentication dependency, defaults --host to 0.0.0.0 and --port to 8002, and mounts CORS with allow_origins=["*"] and allow_credentials=True.

Stack those and the exposure writes itself. A request body of {inputs, session_id, agent_cfg} arrives from anyone who can reach the port. The text in inputs is the thing that steers what the planner emits. Whatever the planner emits inside its first code fence is executed in the API process. The shipped Dockerfile ends with CMD ["python3", "-m", "mindsearch.app", "--asy", "--host", "0.0.0.0", "--port", "8002"], and the project’s own compose template publishes 8002:8002. This is not a hardening-guide failure; it is the documented way to run the thing.

Not an injection into the tool — an injection into the author of the tool

The researcher’s public advisory (gist MRCE.md, dated 22 August 2026) is worth reading for one detail that distinguishes this class from ordinary command injection. The proof of concept does not smuggle Python into a parameter. It sends plain English telling the planner that the “WebSearchGraph Jupyter kernel” is broken unless its interpreter block begins with a health probe — then supplies the probe. The planner, instructed by its own system prompt to behave like a programmer in a notebook, complies and emits the probe at the top of the same fence. MindSearch then executes the fence. The advisory reports reproducing this end-to-end against a live commercial planner model, with the resulting file inside the container showing uid=0(root).

That is the agentic twist. The attacker never needed to satisfy a parser. They needed to persuade a model that was already granted exec(), and the application treated the model’s output as trusted because the application asked for code. It is the same structural error we covered when GitLab’s AI Gateway let a Duo user escape the prompt-template sandbox and when Langflow’s code validator turned into a credential-harvesting foothold — except MindSearch never built a sandbox to escape, and never put a login in front of it.

Two secondary notes from the source that defenders should price in. First, extract_code() matches the first fence in the whole message rather than only the parsed interpreter action, so a payload does not have to be valid graph code to run — exec() happens before anything requires WebSearchGraph to exist. Second, the wildcard CORS with credentials means a developer running MindSearch on loopback is reachable from any page their browser opens, the same drive-by seam that produced the Headroom WebSocket hijack and MetaMCP’s unpatched proxy RCE.

The part that will not be fixed

Severity here is ordinary. Maintenance is not. Checking the repository directly: 6,934 stars, 694 forks, Apache-2.0, exactly one release (v0.1.0, 5 November 2024), and the most recent commit to main dated 4 July 2025 — a README logo tweak. graph.py, the file holding the exec(), has not been touched since November 2024. The repository is not archived, so GitHub still presents it as live. There is no PyPI distribution to pin away from, which means every consumer is running a git checkout or a container built from one, and every one of those is the vulnerable revision.

So the honest status line is: critical, publicly exploited in proof-of-concept form, no patch, no maintainer response, and no upstream likely to produce one. That combination now has a track record on this site — LightLLM carrying ten CVEs with no fixes, a reference MCP server unpatched for four months, and 15,465 MCP servers with abandoned domains in the supply chain. The AI tooling layer is accumulating popular, unmaintained, network-listening code faster than anyone is retiring it, and a star count is not a maintenance commitment.

What to do

  • Find it before you argue about severity. Hunt for listeners on :8002, for processes matching mindsearch.app, and for images derived from mindsearch/backend. Because there is no package manager artefact, SCA tooling that only reads lockfiles will report nothing — the same blind spot we flagged in the MCP SDK advisory wave.
  • Treat exposure as the control, since the code is not changing. Take /solve off any reachable network, bind to loopback, and put authenticated reverse-proxy terminated access in front of it. Wildcard-credentialed CORS means loopback alone is not isolation if a browser runs on the same host.
  • Rotate what the process could read. exec() with process globals means every environment variable in that container — model API keys, search API keys, cloud credentials — is readable by a successful request. If the port was ever exposed, rotate first and investigate second.
  • Apply the general rule to your own stack. If any component of your agent runs model-authored code, the question to answer this week is not “is the prompt filtered” but “what are the privileges, the network reachability, and the secrets of the process calling exec().” Separate unprivileged container, no secrets in environment, no egress, hard timeout — or no interpreter at all.

Verification note: we read the NVD record for CVE-2026-105135 (published 4 October 2026, status Received, VulDB as CNA) and the researcher’s public advisory, then independently confirmed the cited code against the current InternLM/MindSearch repository — exec() in ExecutionAction.run, globals() as the execution namespace, the unauthenticated /solve route, the 0.0.0.0:8002 defaults, the wildcard credentialed CORS, the Dockerfile CMD, and the compose port mapping — plus the repository’s star count, release list and commit dates via the GitHub API. We did not deploy MindSearch, send requests to any instance, or attempt exploitation; the proof-of-concept results and the root-in-container claim are the advisory author’s own. The CVSS 3.1 base of 10.0 is VulDB’s scoring; the researcher’s advisory self-scored the same issue 9.8 under CVSS 3.1.

Sources: