Files
graphify/SECURITY.md
Safi 6695f0aefd fix: security hardening, dedup correctness, and large-graph support
- security.py: replace global socket.getaddrinfo monkey-patch with per-connection
  _SSRFGuardedHTTPConnection/HTTPSConnection subclasses (thread-safe, closes TOCTOU)
- security.py: add GRAPHIFY_MAX_GRAPH_BYTES env var override for 512MB cap (MB/GB suffix
  supported); improve cap error message to cite the env var
- llm.py: wrap untrusted source files in XML delimiters with sha256 fingerprint;
  neutralise jailbreak sentinel tokens to mitigate prompt injection
- dedup.py: skip code nodes in label-based dedup passes; code symbols now deduplicated
  by ID only, preventing distinct same-named symbols from merging
- extract.py: cross-file calls resolution now consults import evidence before bailing
  on ambiguous callee names; emits EXTRACTED edges when named import is unambiguous
- analyze.py: extend _BUILTIN_NOISE_LABELS with stdlib types and modules
- __main__.py: CLAUDE.md template uses MANDATORY language for graphify-first rule;
  PreToolUse hook message hardened to imperative; graphify export html auto-falls
  back to community-aggregation view when graph.json exceeds size cap
- tests/test_pg_introspect.py: add importorskip guard for tree_sitter_sql

Closes #1211, #1210, #1205, #1219, #1227; resolves discussion #1019

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:45:20 +01:00

3.7 KiB

Security Policy

Supported Versions

Version Supported
0.3.x Yes
< 0.3 No

Reporting a Vulnerability

Do not open a public GitHub issue for security vulnerabilities.

Report security issues via GitHub's private vulnerability reporting, or email the maintainer directly. Please include:

  • Description of the vulnerability
  • Steps to reproduce
  • Potential impact
  • Suggested fix (if any)

We will acknowledge receipt within 48 hours and aim to release a fix within 7 days for critical issues.

Security Model

graphify is a local development tool. It runs as a Claude Code skill and optionally as a local MCP stdio server. It makes no network calls during graph analysis - only during ingest (explicit URL fetch by the user).

Threat Surface

Vector Mitigation
SSRF via URL fetch security.validate_url() allows only http and https schemes, blocks private/loopback/link-local IPs, and blocks cloud metadata endpoints. Redirect targets are re-validated. All fetch paths including tweet oEmbed go through safe_fetch().
Oversized downloads safe_fetch() streams responses and aborts at 50 MB. safe_fetch_text() aborts at 10 MB.
Non-2xx HTTP responses safe_fetch() raises HTTPError on non-2xx status codes - error pages are not silently treated as content.
Path traversal in MCP server security.validate_graph_path() resolves paths and requires them to be inside graphify-out/. Also requires the graphify-out/ directory to exist.
XSS in graph HTML output security.sanitize_label() strips control characters, caps at 256 chars, and HTML-escapes all node labels and edge titles before pyvis embeds them.
Prompt injection via node labels sanitize_label() also applied to MCP text output - node labels from user-controlled source files cannot break the text format returned to agents.
Prompt injection via source file content During the semantic pass, source files are attacker-controlled text mixed into the LLM context. _read_files() in llm.py wraps every file in a hash-stamped <untrusted_source path=... sha256=...> delimiter block, the extraction system prompt instructs the model to treat that block as inert data and never as instructions, and _neutralise_injection_sentinels() defangs known chat-template/jailbreak tokens (<|im_start|>, [INST], <<SYS>>, forged </untrusted_source>, etc.) before insertion. This is the table-stakes defense (issue #1210): it does not make injection impossible, but changes it from "works on first try" to "requires evasion."
YAML frontmatter injection _yaml_str() escapes backslashes, double quotes, and newlines before embedding user-controlled strings (webpage titles, query questions) in YAML frontmatter.
Encoding crashes on source files All tree-sitter byte slices decoded with errors="replace" - non-UTF-8 source files degrade gracefully instead of crashing extraction.
Symlink traversal os.walk(..., followlinks=False) is explicit throughout detect.py.
Corrupted graph.json _load_graph() in serve.py wraps json.JSONDecodeError and prints a clear recovery message instead of crashing.

What graphify does NOT do

  • Does not run a network listener (MCP server communicates over stdio only)
  • Does not execute code from source files (tree-sitter parses ASTs - no eval/exec)
  • Does not use shell=True in any subprocess call
  • Does not store credentials or API keys

Optional network calls

  • ingest subcommand: fetches URLs explicitly provided by the user
  • PDF extraction: reads local files only (pypdf does not make network calls)
  • watch mode: local filesystem events only (watchdog does not make network calls)