From 863100cb060bf0bf7cbbd3e656f121a20511f0eb Mon Sep 17 00:00:00 2001 From: Safi Date: Fri, 10 Apr 2026 01:08:15 +0100 Subject: [PATCH] fix MCP server path validation security issue (0.3.26) --- CHANGELOG.md | 4 ++++ graphify/serve.py | 9 +++++++-- pyproject.toml | 2 +- tests/test_serve.py | 5 +---- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6b3d36d..afa67ce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Full release notes with details on each version: [GitHub Releases](https://github.com/safishamsi/graphify/releases) +## 0.3.26 (2026-04-10) + +- Fix: MCP server no longer uses a circular path validation when loading a graph outside cwd — now validates the path exists and ends in `.json` instead of checking containment within its own parent directory (security fix) + ## 0.3.25 (2026-04-09) - Fix: `graphify install --platform gemini` now routes to `gemini_install()` instead of erroring — `gemini` was missing from `_PLATFORM_CONFIG` (#171) diff --git a/graphify/serve.py b/graphify/serve.py index 9f42c2c0..81c9353a 100644 --- a/graphify/serve.py +++ b/graphify/serve.py @@ -5,12 +5,17 @@ import sys from pathlib import Path import networkx as nx from networkx.readwrite import json_graph -from graphify.security import validate_graph_path, sanitize_label +from graphify.security import sanitize_label def _load_graph(graph_path: str) -> nx.Graph: try: - safe = validate_graph_path(graph_path, base=Path(graph_path).resolve().parent) + resolved = Path(graph_path).resolve() + if resolved.suffix != ".json": + raise ValueError(f"Graph path must be a .json file, got: {graph_path!r}") + if not resolved.exists(): + raise FileNotFoundError(f"Graph file not found: {resolved}") + safe = resolved data = json.loads(safe.read_text()) try: return json_graph.node_link_graph(data, edges="links") diff --git a/pyproject.toml b/pyproject.toml index 6dafb567..71e90a15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "graphifyy" -version = "0.3.25" +version = "0.3.26" description = "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, OpenClaw, Factory Droid, Trae) - turn any folder of code, docs, papers, or images into a queryable knowledge graph" readme = "README.md" license = { file = "LICENSE" } diff --git a/tests/test_serve.py b/tests/test_serve.py index ed521765..6457ac50 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -138,14 +138,11 @@ def test_subgraph_to_text_edge_included(): # --- _load_graph --- def test_load_graph_roundtrip(tmp_path): - from unittest.mock import patch G = _make_graph() data = json_graph.node_link_data(G, edges="links") p = tmp_path / "graph.json" p.write_text(json.dumps(data)) - # validate_graph_path is tested separately; here we test parse correctness - with patch("graphify.serve.validate_graph_path", return_value=p): - G2 = _load_graph(str(p)) + G2 = _load_graph(str(p)) assert G2.number_of_nodes() == G.number_of_nodes() assert G2.number_of_edges() == G.number_of_edges()