Skip to the content.

CodeGraphContext/CodeGraphContext — security scan

Repository: CodeGraphContext/CodeGraphContext Commit scanned: e839c95e488e41b45bc96ab1b1968bf3da90b7aa Scan date: 2026-07-26 Disclosure status: public — post-only (strict-norm: private-reporting SECURITY.md — email-only, coordinated disclosure)

Summary

Severity Count
Critical 0
High 32
Medium 80
Low 0
Info 0

Total findings: 112 (0 real after curation)

CodeGraphContext (CGC, 4.0k★, MIT) is an MCP server plus a CLI that indexes a local codebase into a graph database — Neo4j, embedded KùzuDB, or FalkorDB — so an AI assistant can ask structural questions (“who calls this function?”, “what inherits from this class?”) over a real code graph instead of a flat grep. That is a rich, dangerous-looking surface for a security tool to chew on: it reads arbitrary source trees off disk, builds and runs graph queries, ships an optional HTTP/SSE gateway advertised for ChatGPT Actions and remote agents, a VSCode extension, a local visualization server, and a marketing website. For a code-graph MCP tool the three questions are: is the graph-query path injectable?, is the optional gateway an unauthenticated network exposure?, and are the database credentials and embedded secrets handled safely? All three close — and they close because the maintainer has already done the security work, out loud, in the code.

Top findings (all curated out)

1. 12 graph-query “SQL highs” — internal DDL, the purest identifier FP in the series

Every flagged self._conn.execute(f"…") is schema DDL, not a data query: CREATE NODE TABLE, CREATE REL TABLE GROUP, ALTER TABLE … ADD. The only interpolated tokens are the table_name, schema, and column_type fields — and each one is drawn from a hardcoded constant tuple list inside the same module (node_tables, rel_tables, simple_migrations, _CALLS_SUBTABLES, …). No caller-supplied string reaches any of them; there is no data value in these statements at all. This is the identifier FP in its purest form — Kùzu’s schema builder writing its own fixed schema.

2. wildcard-cors on the gateway — paired with allow_credentials=False, and commented

The allow_origins=["*"] is exactly the config semgrep flags, but it sits beside allow_credentials=False and a comment that explains why: “Credentials must stay disabled while origins is a wildcard; the combination is rejected by browsers and would leak cookie-authed responses to any site.” Wildcard CORS is only dangerous with credentials on — the developer named the trap and stayed out of it.

3. Optional HTTP gateway — auth exists, warns loudly, but defaults to 0.0.0.0

The primary MCP transport is STDIO (server.run() reads JSON-RPC off stdin — local, no network). The gateway is a separate, opt-in command, cgc api start. Its auth story is genuinely well-built: require_api_key is a router-wide FastAPI dependency (APIRouter(dependencies=[Depends(require_api_key)])), uses secrets.compare_digest for constant-time comparison, is backward-compatible opt-in via CGC_API_KEY, and — when unset — logs a prominent startup warning that names the exact exposure: “CodeGraphContext API is running WITHOUT authentication. Anyone who can reach this server can index code, run Cypher queries and call tools.” The one residual is that cgc api start defaults its --host option to 0.0.0.0 while auth is opt-in, so an operator who exposes the gateway without setting a key binds an unauthenticated Cypher/tool endpoint to all interfaces (the same class as the resolved code-graph-rag #808). The safer default would be 127.0.0.1, requiring an explicit --host 0.0.0.0 to publish. That said, the project has already pre-mitigated most of this: the auth mechanism ships, the warning ships, and the API reference documents cgc api start --host 127.0.0.1 as an option. It is a secure-by-default nit on a feature the maintainer clearly understands.

4. Neo4j credentials — no default password, fails closed

neo4j_password = os.getenv('NEO4J_PASSWORD') has no fallback default — the driver raises Neo4jConnectionError with actionable guidance if it is unset, and even fast-fails on an unreachable host before building a driver. The username defaults to the standard neo4j; there is no shipped default password to guess. The neo4j/12345678 that gitleaks surfaced is a throwaway CI service-container password in db-parity-check.yml, not a real credential.

5. Secrets — Supabase anon keys (public by design) + generated protobuf + CI material

The three JWTs are Supabase anon keys embedded in the marketing website’s frontend — public by design (row-level security enforces access; they are meant to ship to the browser), the same class as the publishable keys credited on IBM ContextForge. The generic-api-key in scip_pb2.py is the serialized_pb=b"…" descriptor bytes of a generated protobuf module, not a secret.

Patterns observed

This is the 24th clean scan in the series, and one of the cleaner security-aware codebases in it — not clean because the surface is small (112 findings, a full HTTP gateway, three graph backends, a VSCode extension, and a website), but because the maintainer keeps writing the mitigation right next to the risk. The CORS wildcard carries the credentials caveat in a comment; the unauthenticated gateway logs a warning that spells out precisely what an attacker could do; the Kùzu migration code, like Osmantic/ODS’s token-store before it, validates identifiers it already controls. There is even a tests/unit/utils/test_path_sandbox_security.py guarding the indexer’s path-confinement — a test suite for a threat the scanner never raised.

The count is, once again, structural rather than substantive. Two rule families account for most of it: 43 github-actions-mutable-action-tag mediums (SHA-pin hygiene) and the 12 graph-DDL identifier “highs”. The genuine residuals are the familiar reachability-gated tail — a website/ frontend dependency refresh (lodash, PostCSS, ws, react-router — the visualization UI’s npm tree, a Kiln-shaped lockfile drift) and a transitive protobuf 3.20.3 bump behind the Neo4j driver. The one exception to “all FP” is a genuine secure-by-default hardening opinion (the 0.0.0.0 gateway default), and even that the project has mostly answered already.

The XML parsers deserve a specific note: maven.py and mybatis.py both use stdlib xml.etree.ElementTree.parse() on pom.xml / *Mapper.xml files as they index a target repo, which trips use-defused-xml-parse. As on KiCAD-MCP, this is DoS-only — CPython’s etree does not resolve external entities, so the exposure is billion-laughs entity expansion on a maliciously-crafted file the operator chose to index, not XXE file-read or SSRF. Swapping in defusedxml is still worth doing as defense-in-depth, and it is the single most concrete code change the whole scan produced.

Notes on the tool

Disclosure timeline

Reproduce

git clone https://github.com/CodeGraphContext/CodeGraphContext /tmp/scan-target
python scanner/run_scan.py --repo /tmp/scan-target --reports-dir ./reports/codegraphcontext-codegraphcontext --min-severity medium

This scan is a probabilistic signal from an automated toolchain plus manual curation, not a security audit or a guarantee. Findings are dispositioned in good faith; a “0 real” result means nothing rose above the noise floor on this commit, not that the code is free of vulnerabilities.