Skip to the content.

vitali87/code-graph-rag - security scan

Repository: vitali87/code-graph-rag Commit scanned: 6f3da01fb2495b5b6f1c33c2449defb43dcca6a3 Scan date: 2026-07-19 Disclosure status: ✅ resolved — fix merged upstream (PR #809, 2026-07-19)

Summary

Severity Count
Critical 0
High 5
Medium 17
Low 0
Info 0

Total findings: 22 (1 real, reachable, exploitability-shaped after curation)

What it is

Code-Graph-RAG is a graph-based RAG system that parses multi-language codebases with Tree-sitter, builds a Memgraph knowledge graph, and answers natural-language queries about code — including editing capabilities. It ships as an MCP server (codebase_rag/mcp/server.py) so an agent can drive it. That MCP surface is where the one real finding lives.

Top findings

1. Reachable MCP SDK CVEs — the exposed StreamableHTTP transport binds 0.0.0.0 with no auth

Reachability breakdown (version-match ≠ reachable)

CVE Transport affected Used here? Verdict
CVE-2026-52869 Streamable HTTP (unauth’d principal) Yesserve_http exposed via cli.py, 0.0.0.0:8080, no auth Reachable
CVE-2026-52870 Experimental task handlers Conditional (SDK feature) Cleared by same bump
CVE-2026-59950 WebSocket transport (Host/Origin) No — no WS transport in the code Version-match only

The honest version: only one of the three is a live, reachable exposure on the default deployment; a second is conditional; the WebSocket CVE is inert because the server never speaks WebSocket. One dependency bump resolves all three regardless.

The other four “highs” are false positives

Patterns observed

The interesting move on this repo is that the RAG engine is a code-editor: it runs model-driven Tree-sitter parsing and writes files, so the reflex is to hunt for an exec/injection sink. The scanner dutifully lit up six non-literal-import hits in parser_loader.py and the parser modules — but every one is dynamic Tree-sitter grammar loading where the language name is typed cs.SupportedLanguage (a constrained enum), not a path an attacker names. That’s the product loading its own grammars, not an import-injection. Same story for the two dynamic-urllib hits: one fetches a host-pinned https://pypi.org/pypi/{pkg}/json summary for the project’s own dependencies (README generation — host is fixed, only the package path segment varies), the other is a local dev-stack health probe with a timeout, already carrying a # noqa: S310. Neither is SSRF.

So the real signal wasn’t in the Python logic at all — it was in the dependency + deployment posture of the MCP transport. The SDK is pinned to a version with three published CVEs, and the project happens to expose the one transport (StreamableHTTP) that the most serious of them targets, on 0.0.0.0 with no auth. This is the distinction I keep coming back to across these scans: a version-match on mcp means little until you check which transport the project actually stands up. Here it stands up the vulnerable one by default — that’s what promotes it from noise to a finding.

Two genuine-but-minor hardening notes round it out: split-score.yml pins actions/checkout@v4 and vitali87/pr-split@v1.0.0 by mutable tag while every other workflow in the repo SHA-pins (actions/checkout@de0fac2…) — worth making consistent. Everything else — the six evals/oracles/*.js path-join hits, the optimize/code_to_text.sh IFS note — is eval-harness and dev-tooling code, outside the shipped package.

Notes on the tool

Disclosure timeline

Reproduce

git clone https://github.com/vitali87/code-graph-rag /tmp/scan-target
python scanner/run_scan.py --repo /tmp/scan-target --reports-dir ./reports/vitali87-code-graph-rag --min-severity medium

AI PatchLab is a local-first security scanner. This report was generated locally — no source code was sent to any third party and no AI provider was contacted. Findings are a signal, not a verdict; the reachability and exploitability judgments above are mine after reading the call sites.