# Risk map — Graphify-Labs/graphify

> Canonical page: https://codevetter.com/unpack/graphify/risk-map

# Risk map — Graphify-Labs/graphify

Scanned commit 20a20d30d8e7eef77675651f0199d87f913bd3e7 (2026-09-20). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The main risks are (a) SSRF/SSRF-adjacent fetching — mitigated by a purpose-built guarded-connection stack; (b) prompt injection from attacker-controlled corpus files into the semantic LLM pass — mitigated but explicitly 'table stakes'; (c) the 8,300-line extract.py monolith mid-migration where the shared _extract_generic core and cross-file resolvers concentrate most defect density; (d) shell-out paths (git hooks, detached rebuilds, gh/git subprocesses, claude CLI) with heavy escaping/Windows edge-case handling; and (e) graph.json integrity guards (size cap, shrink guard, path validation) that protect but can also mask silent data loss if misread.
- All outbound fetching goes through security.py: validate_url allows only http/https, blocks private/reserved/loopback/link-local/CGN ranges and cloud metadata hosts, and _SSRFGuarded*Connection subclasses resolve+validate DNS once then connect to that exact IP to defeat DNS-rebind TOCTOU; redirects are re-validated by _NoFileRedirectHandler. — [graphify/security.py#L83-251](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L83-L251)
- Fetch size caps: safe_fetch aborts streaming reads at 50 MB (safe_fetch_text at 10 MB) and raises HTTPError on non-2xx; graph-file loads are capped at 512 MiB (GRAPHIFY_MAX_GRAPH_BYTES) to prevent memory-bomb json.loads. — [graphify/security.py#L20-66](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L20-L66) [graphify/security.py#L258-308](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L258-L308)
- Prompt-injection surface: corpus files are wrapped in <untrusted_source sha256=...> blocks, _neutralise_injection_sentinels defangs chat-template tokens ([INST], <<SYS>>, forged closing tags), and sanitize_label/sanitize_metadata strip control chars + HTML-escape before export/MCP output — but SECURITY.md itself calls this 'table-stakes' and admits evasion is possible. — [graphify/llm.py#L572-598](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L572-L598) [graphify/security.py#L394-460](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/security.py#L394-L460) [SECURITY.md](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/SECURITY.md)
- detect.py silently skips credential-shaped files (.env, .pem/.key/.p12, id_rsa/id_ed25519, .npmrc/.pypirc/.git-credentials/.boto, secring) and refuses to descend into .ssh/.gnupg/.aws/.gcloud dirs — an attacker naming real source `id_rsa.py`-style or a user pointing graphify at a secrets dir still loses coverage silently. — [graphify/detect.py#L110-147](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/detect.py#L110-L147)
- The HTTP MCP transport is opt-in and unauthenticated by default: serve_http warns loudly when binding 0.0.0.0 without --api-key/GRAPHIFY_API_KEY, and _ApiKeyMiddleware does a constant-time compare of X-API-Key/Bearer tokens — but a user can still expose the graph read-only to the network. — [graphify/serve.py#L2447-2470](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2447-L2470) [graphify/serve.py#L2561-2615](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L2561-L2615) [Dockerfile#L1-9](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/Dockerfile#L1-L9)
- extract.py is ~8,300 lines holding the shared _extract_generic core plus ~20 in-place cross-file resolution passes (member calls for swift/python/typescript/cpp/csharp/java/objc/kotlin/rust, qualified calls, interface dispatch); extractors/MIGRATION.md documents the ongoing one-language-per-PR extraction — this is the blast-radius hotspot where most issue-numbered regressions live. — [graphify/extract.py#L3003-5256](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L3003-L5256) [graphify/extractors/MIGRATION.md#L1-34](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extractors/MIGRATION.md#L1-L34)
- hooks.py embeds multi-stage shell+Python payloads in Python string constants with strict character constraints (no `"`, `$`, backtick, backslash inside the -c payload); a careless edit to _REBUILD_BODY_* or _LAUNCHER_TEMPLATE can silently break every installed hook — and the failure mode is a stale graph, not an error. — [graphify/hooks.py#L140-352](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/hooks.py#L140-L352)
- Shell-outs: prs.py shells to `gh`/`git` (subprocess.run, no shell=True), cli._clone_repo runs git clone, llm._call_claude_cli shells to the claude binary, transcribe.py invokes yt-dlp — each is a supply-chain/PATH dependency that fails closed only if the binary is absent. — [graphify/prs.py#L140-215](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/prs.py#L140-L215) [graphify/cli.py#L1000-1058](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L1000-L1058) [graphify/llm.py#L1675-1730](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/llm.py#L1675-L1730)
- install.py writes into user config dirs (~/.claude, ~/.codex, ~/.config/..., ~/.agents) and mutates assistant config files (settings.json hooks, AGENTS.md/CLAUDE.md sections) — a mis-install or stale uninstaller can corrupt the user's assistant setup; the conftest HOME sandbox exists precisely because this is dangerous in tests. — [graphify/install.py#L1-30](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/install.py#L1-L30) [tests/conftest.py#L50-65](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/tests/conftest.py#L50-L65)
- scip_ingest.py is a self-declared skeleton ('NOT a full SCIP protobuf implementation ... Not wired to the CLI in this phase') — dead/legacy code carrying an ingestion surface with no caller or test coverage beyond import checks. — [graphify/scip_ingest.py#L1-20](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/scip_ingest.py#L1-L20)
- Graph-integrity guards can mask failure modes: the to_json shrink guard (#479) refuses to overwrite a larger graph (good) but requires users to discover --force/GRAPHIFY_FORCE; the semantic 'unverified shrink' guard similarly stalls writes until --allow-partial; corrupt graph.json exits the process on CLI paths while MCP converts it to tool errors. — [graphify/export.py#L229-271](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/export.py#L229-L271) [graphify/cli.py#L171-218](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L171-L218) [graphify/serve.py#L86-91](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/serve.py#L86-L91)
- Incremental correctness depends on manifest stamping discipline — a long trail of fixed bugs (#933, #1666, #1897, #1909, #1941, #2015, #2543) shows files could be silently marked done with no extraction output; _stamped_manifest_files is the single chokepoint an agent must not weaken. — [graphify/cli.py#L88-168](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L88-L168) [graphify/cli.py#L221-250](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/cli.py#L221-L250)
- Windows portability is a recurring defect class addressed throughout (os_replace_with_fallback for WinError 5/32/17, MAX_PATH filename budgets, 61-worker process cap, PowerShell-safe hook payloads) — code touching filesystem identity or subprocess spawning must handle both platforms explicitly. — [graphify/paths.py#L29-99](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L29-L99) [graphify/paths.py#L378-447](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/paths.py#L378-L447) [graphify/extract.py#L6640-6655](https://github.com/Graphify-Labs/graphify/blob/20a20d30d8e7eef77675651f0199d87f913bd3e7/graphify/extract.py#L6640-L6655)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
