Summary
The /api/graph endpoint reads the entire data/estorides_graph.graphml file from disk, deserialises it with NetworkX, runs Louvain community detection, and serialises the result on every request. This file is shared across all users of the deployment — whoever ran the last query owns the graph. An anonymous caller requesting /api/graph receives the full intelligence graph from the last investigation run by any operator. There is also no rate limit on this endpoint (separate issue), so it can be called indefinitely.
Evidence
estorides_web.py lines 232–305:
@app.route("/api/graph")
def api_graph() -> Any:
if not GRAPH_PATH.exists():
return jsonify({"nodes": [], "edges": []})
import networkx as nx
kg = KnowledgeGraph()
kg.graph = nx.read_graphml(GRAPH_PATH) # reads shared file
...
return jsonify({"nodes": nodes, "edges": edges, ...})
GRAPH_PATH is defined in estorides_core/config.py line 77:
GRAPH_PATH: Path = DATA_DIR / "estorides_graph.graphml"
This is a single file, written by the last /api/run call, readable by any HTTP client that hits /api/graph.
Why this matters
In a multi-user or shared deployment, /api/graph leaks the entire intelligence graph of the previous investigator's query to any subsequent caller, including anonymous ones. This exposes:
- All entity types and values (IPs, domains, emails, CVEs) from the previous investigation
- Relationship structure between entities
- Which OSINT sources observed which entities
Attack or failure scenario
- Security researcher uses the platform to investigate a confidential threat actor.
- A second party (attacker or competitor) immediately calls
GET /api/graph.
- They receive the full intelligence picture of the researcher's investigation without any authentication.
Root cause
The graph file is a global singleton written without a per-session or per-case namespace. This was acceptable for a single-user local tool but becomes a data disclosure issue in any multi-user or web-accessible deployment.
Recommended fix
- Namespace the graph file per case:
data/graphs/<case_id>.graphml.
- Require the caller to pass a
?case_id=<id> parameter; validate that the file exists and serve only that case's graph.
- If backward compatibility is needed, keep the global graph file for CLI use but refuse to serve it via the web API without a case parameter.
- Add authentication before any
/api/graph response.
Acceptance criteria
/api/graph requires a case_id parameter.
- Each
/api/run call stores its graph under a case-scoped filename.
- No anonymous caller can retrieve another user's intelligence graph via
/api/graph.
- The UI passes the active case ID when requesting the graph.
Suggested labels
security, privacy, architecture
Priority
P1
Severity
High — Any caller can retrieve the complete intelligence graph of the most recent investigation, including all discovered entities and relationships, without authentication.
Confidence
Confirmed — the shared global graph file path is hardcoded in config.py and served without access control.
Summary
The
/api/graphendpoint reads the entiredata/estorides_graph.graphmlfile from disk, deserialises it with NetworkX, runs Louvain community detection, and serialises the result on every request. This file is shared across all users of the deployment — whoever ran the last query owns the graph. An anonymous caller requesting/api/graphreceives the full intelligence graph from the last investigation run by any operator. There is also no rate limit on this endpoint (separate issue), so it can be called indefinitely.Evidence
estorides_web.pylines 232–305:GRAPH_PATHis defined inestorides_core/config.pyline 77:This is a single file, written by the last
/api/runcall, readable by any HTTP client that hits/api/graph.Why this matters
In a multi-user or shared deployment,
/api/graphleaks the entire intelligence graph of the previous investigator's query to any subsequent caller, including anonymous ones. This exposes:Attack or failure scenario
GET /api/graph.Root cause
The graph file is a global singleton written without a per-session or per-case namespace. This was acceptable for a single-user local tool but becomes a data disclosure issue in any multi-user or web-accessible deployment.
Recommended fix
data/graphs/<case_id>.graphml.?case_id=<id>parameter; validate that the file exists and serve only that case's graph./api/graphresponse.Acceptance criteria
/api/graphrequires acase_idparameter./api/runcall stores its graph under a case-scoped filename./api/graph.Suggested labels
security, privacy, architecture
Priority
P1
Severity
High — Any caller can retrieve the complete intelligence graph of the most recent investigation, including all discovered entities and relationships, without authentication.
Confidence
Confirmed — the shared global graph file path is hardcoded in config.py and served without access control.