Skip to content

fix(security): GHSA-gvpp-v77h-5w8g — untrusted dev-source ACE#47

Merged
cdeust merged 2 commits into
mainfrom
security/ghsa-gvpp-v77h-5w8g
May 27, 2026
Merged

fix(security): GHSA-gvpp-v77h-5w8g — untrusted dev-source ACE#47
cdeust merged 2 commits into
mainfrom
security/ghsa-gvpp-v77h-5w8g

Conversation

@cdeust
Copy link
Copy Markdown
Owner

@cdeust cdeust commented May 27, 2026

Summary

Closes the local arbitrary code execution vulnerability GHSA-gvpp-v77h-5w8g reported by EQSTLab on 2026-05-27. CVSS v3.1 base 7.8 (High). Vulnerable range: neuro-cortex-memory ≤ 3.17.0.

Threat model (verbatim from advisory)

_find_dev_source() in mcp_server/handlers/open_visualization.py accepted CLAUDE_PROJECT_DIR (set automatically by Claude Code to whatever project the user has open) as a candidate Cortex dev-source root. A candidate qualified iff it contained a trivial two-marker pair — mcp_server/ subdirectory + ui/unified-viz.html file — that any attacker can replicate.

The handler then subprocess.run([sys.executable, str(bootstrap_path)]) against <dev_src>/mcp_server/server/visualize_bootstrap.py inside the qualifying directory, yielding local arbitrary code execution with the privileges of the victim's user process.

Two secondary surfaces:

  • mcp_server/server/http_launcher.py::_detect_dev_source mirrored the same env-var ordering and rsyncs the returned dev source over the package path before respawning the server from the synced copy.
  • mcp_server/server/visualize_bootstrap.py::_find_dev_source (invoked as a subprocess of the handler) inherited the parent process env and re-resolved CLAUDE_PROJECT_DIR → same rsync-overwrite path → persistent ACE.

Hardening

Three functions (open_visualization._find_dev_source, http_launcher._detect_dev_source, visualize_bootstrap._find_dev_source) now apply the same gate:

  • CLAUDE_PROJECT_DIR is no longer consulted in any of them.
  • CORTEX_DEV_ROOT is consulted only when CORTEX_DEV_SOURCE_SYNC=1 is set as an explicit opt-in — exact string "1" required (so an accidental "true"/"yes" in a shell rc file cannot silently re-open the hole).
  • Launcher's filesystem-position auto-detect ("walk up from the launcher module's own location") is preserved — an attacker would need write access to the user's site-packages to abuse it, which is a strictly higher-privilege precondition than the exploit it would yield.
  • The conventional ~/Documents/Developments/Cortex fallback remains (user-controlled filesystem).

Verification

  • EQSTLab PoC re-run against the patched code: bootstrap='no_dev_source', sentinel file never created, no rsync, no subprocess against the attacker path.
  • Legitimate dev workflow (CORTEX_DEV_SOURCE_SYNC=1 + CORTEX_DEV_ROOT pointing at a real checkout): still works, bootstrap fires as expected.
  • 10 falsification tests added across two files:
    • tests_py/handlers/test_open_visualization.py::TestDevSourceSecurityHardening (4 tests — handler)
    • tests_py/server/test_http_launcher_security.py::TestDetectDevSourceSecurityHardening (3 tests — launcher)
    • tests_py/server/test_http_launcher_security.py::TestBootstrapFindDevSourceSecurityHardening (3 tests — bootstrap subprocess)
  • Full open_visualization suite: 17/17 passing (4 pre-existing + 13 new).
  • Audit of sibling repos (automatised-pipeline, ai-prd-generator, ai-prd-generator-plugin, zetetic-team-subagents): clean of the same class of vulnerability (no CLAUDE_PROJECT_DIR consumption, no env-derived subprocess.run/Command::new, no marker-file validation followed by exec).

Release plan

After merge:

  1. Tag v3.17.1 on main
  2. Build wheel + sdist, publish to PyPI
  3. Yank 3.17.0 from PyPI with reason citing GHSA-gvpp-v77h-5w8g
  4. Publish the GHSA advisory crediting EQSTLab as reporter

Credit

Reported by @EQSTLab (Experts, Qualified Security Team — SK Shieldus). Coordinated disclosure via private GHSA draft, complete with structured PoC and a precise remediation diff. Thanks for the careful, rigorous report.

source: GHSA-gvpp-v77h-5w8g

🤖 Generated with Claude Code

cdeust and others added 2 commits May 27, 2026 11:08
The open_visualization tool resolved candidate Cortex dev-source roots
via the CLAUDE_PROJECT_DIR env var (set automatically by Claude Code to
whichever project the user has open). A candidate qualified iff it
contained a trivial two-marker pair — an mcp_server/ subdirectory and a
ui/unified-viz.html file — that any attacker can replicate in a
malicious repository. The handler then subprocess.run([sys.executable,
str(bootstrap_path)]) against mcp_server/server/visualize_bootstrap.py
inside the qualifying directory, yielding local arbitrary code
execution with the privileges of the victim's user process.

A secondary code-execution path went through http_launcher._detect_dev_
source which mirrored the same env-var ordering; the launcher rsyncs
the returned dev source over the package path and respawns the
visualization server from the synced copy.

Reported by EQSTLab (SK Shieldus security research) on 2026-05-27.
CVSS v3.1 base 7.8 (HIGH). Vulnerable range: ≤ 3.17.0.

Hardening (per advisory recommendation):

  * CLAUDE_PROJECT_DIR is no longer consulted by either function.
  * CORTEX_DEV_ROOT is consulted only when the user has also set
    CORTEX_DEV_SOURCE_SYNC=1 — an explicit opt-in that signals
    "I deliberately want my CORTEX_DEV_ROOT to be used as a
    code-execution dev source."
  * The exact string "1" is required to activate the gate (so an
    accidental "true"/"yes" in a shell rc file cannot silently
    re-open the hole).
  * Launcher's filesystem-position auto-detect ("walk up from the
    launcher module's own location") is preserved — an attacker
    would need write access to the user's site-packages to abuse
    it, which is a strictly higher-privilege precondition than the
    exploit it would yield.
  * Conventional ~/Documents/Developments/Cortex fallback is
    preserved — user-controlled filesystem; attacker would already
    need write access under $HOME to abuse it.

Falsification tests added in tests_py/handlers/test_open_visualization
.py::TestDevSourceSecurityHardening and tests_py/server/test_http_
launcher_security.py::TestDetectDevSourceSecurityHardening — 7 tests,
all green. Each test plants the two marker files plus the bootstrap
PoC into a tempdir, points the relevant env var at it, and asserts
the function refuses to return it.

Version bumped to 3.17.1.

source: GHSA-gvpp-v77h-5w8g
        EQSTLab, "Untrusted Project Bootstrap Code Execution via
        CLAUDE_PROJECT_DIR" (2026-05-27).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI lint job flagged F401 on the added security regression tests in
PR #47. Both imports were leftovers from an earlier iteration that
used monkeypatch-as-fixture imperatively; the final tests use the
pytest-injected monkeypatch fixture and tempfile, neither of which
requires the os or pytest top-level imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cdeust cdeust merged commit 5d22091 into main May 27, 2026
11 checks passed
cdeust added a commit that referenced this pull request May 27, 2026
…lace

v3.17.1 shipped the security fix CODE to the marketplace, but the
release only bumped pyproject.toml — .claude-plugin/marketplace.json
still advertised 3.17.0. Because Claude Code decides whether to prompt
a /plugin update by comparing the installed version against the
marketplace-advertised version, users sitting on 3.17.0 were never
prompted to pull the fix, even though it was present in the cloned
tree. The patched code was in the channel but unadvertised.

This bump aligns all version labels at 3.17.2 so the marketplace
advertises an increment and the update prompt fires:

  - .claude-plugin/marketplace.json  metadata.version  3.17.0 -> 3.17.2
  - .claude-plugin/marketplace.json  plugins[0].version 3.17.0 -> 3.17.2
  - pyproject.toml                   version           3.17.1 -> 3.17.2
  - added plugins[0].version_note pointing at the advisory

No code change — the fix already landed in 5d22091 (PR #47). This is
purely the distribution-metadata correction that makes the marketplace
(the only supported install path per ADR-0050) surface it as an update.

source: GHSA-gvpp-v77h-5w8g; ADR-0050 (marketplace-only distribution).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cdeust cdeust deleted the security/ghsa-gvpp-v77h-5w8g branch May 27, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant