- graphify/security.py (new): centralised security module
- validate_url(): blocks file://, ftp://, data:, any non-http/https scheme
- _NoFileRedirectHandler: re-validates redirect targets, blocks file:// redirects
- safe_fetch(): streams response, 50MB hard cap, non-2xx raises, timeout
- safe_fetch_text(): safe_fetch + UTF-8 decode with errors=replace
- validate_graph_path(): resolves path, requires inside .graphify/, base must exist
- sanitize_label(): strip control chars, cap 256, html.escape() — mirrors
code-review-graph's _sanitize_name pattern
- graphify/ingest.py: _fetch_html() and _download_binary() now use safe_fetch*;
ingest() validates URL scheme and wraps network calls in try/except;
YAML frontmatter: newlines stripped from question before embedding
- graphify/extract.py: all 33 bare .decode() → .decode("utf-8", errors="replace")
— non-UTF-8 source files degrade gracefully instead of crashing extraction
- graphify/export.py: sanitize_label() on all node labels and edge titles
before pyvis embeds them in HTML output
- graphify/serve.py: _load_graph() validates graph_path via validate_graph_path()
and wraps JSONDecodeError with recovery message; sanitize_label() on MCP
text output
- graphify/detect.py: os.walk(..., followlinks=False) made explicit
- SECURITY.md (new): threat model, mitigations table, reporting process
- tests/test_security.py (new): 20 tests covering all security.py functions
54 lines
2.9 KiB
Markdown
54 lines
2.9 KiB
Markdown
# Security Policy
|
|
|
|
## Supported Versions
|
|
|
|
| Version | Supported |
|
|
|---------|-----------|
|
|
| 0.1.x | Yes |
|
|
| < 0.1 | 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. Redirect targets are re-validated by `_NoFileRedirectHandler` — a redirect to `file://` is blocked. |
|
|
| 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/`. Also requires the `.graphify/` 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. |
|
|
| YAML frontmatter injection | Newlines stripped from user-provided strings before embedding in YAML frontmatter (e.g. in `save_query_result()`). |
|
|
| 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)
|