Two pre-existing hardening gaps in graph-traversal depth, surfaced while reviewing #883. Both exist on main today, independent of that PR — #883 would amplify both (it rewrites cbm_store_bfs to enumerate simple paths, turning these from polynomial into exponential exposure), which is why they're worth tracking as a companion to the expensive-expansion guard #883 defers.
1. trace_call_path depth is unclamped (MCP_MAX_DEPTH defined but never used)
MCP_MAX_DEPTH = 15 is declared at src/mcp/mcp.c:17 but referenced nowhere. The trace_call_path handler reads the client depth directly, with only a default and no ceiling:
// src/mcp/mcp.c:2943
int depth = cbm_mcp_get_int_arg(args, "depth", MCP_DEFAULT_DEPTH);
A client can pass an arbitrarily large depth. Today that's "only" a polynomial BFS bounded by graph size, but it's still unbounded client input driving server work. The same unclamped pattern is at src/mcp/mcp.c:5203 (detect_changes).
Fix: clamp depth to MCP_MAX_DEPTH — the constant that already exists for exactly this purpose.
2. Explicit var-length upper bound *1..N is not capped
In expand_var_length (src/cypher/cypher.c:2889):
int max_depth = rel->max_hops > 0 ? rel->max_hops : CYP_MAX_DEPTH;
The CYP_MAX_DEPTH = 10 cap applies only to the unbounded forms (*, *n.., *..m → max_hops = 0). An explicit bound like [:CALLS*1..1000000] passes max_depth = 1000000 straight into cbm_store_bfs.
Fix: cap max_depth at CYP_MAX_DEPTH regardless of whether the bound is explicit — e.g. min(rel->max_hops, CYP_MAX_DEPTH) when > 0.
Notes
Both are small and self-contained. Each wants a reproduce-first test: a deep / large-bound query that the clamp turns from "runs to graph size" into "capped at the ceiling". Landing these alongside (or ahead of) #883's expensive-expansion guard closes the untrusted-depth surface on the shared cbm_store_bfs path.
Two pre-existing hardening gaps in graph-traversal depth, surfaced while reviewing #883. Both exist on
maintoday, independent of that PR — #883 would amplify both (it rewritescbm_store_bfsto enumerate simple paths, turning these from polynomial into exponential exposure), which is why they're worth tracking as a companion to the expensive-expansion guard #883 defers.1.
trace_call_pathdepth is unclamped (MCP_MAX_DEPTHdefined but never used)MCP_MAX_DEPTH = 15is declared atsrc/mcp/mcp.c:17but referenced nowhere. Thetrace_call_pathhandler reads the clientdepthdirectly, with only a default and no ceiling:A client can pass an arbitrarily large
depth. Today that's "only" a polynomial BFS bounded by graph size, but it's still unbounded client input driving server work. The same unclamped pattern is atsrc/mcp/mcp.c:5203(detect_changes).Fix: clamp
depthtoMCP_MAX_DEPTH— the constant that already exists for exactly this purpose.2. Explicit var-length upper bound
*1..Nis not cappedIn
expand_var_length(src/cypher/cypher.c:2889):The
CYP_MAX_DEPTH = 10cap applies only to the unbounded forms (*,*n..,*..m→max_hops = 0). An explicit bound like[:CALLS*1..1000000]passesmax_depth = 1000000straight intocbm_store_bfs.Fix: cap
max_depthatCYP_MAX_DEPTHregardless of whether the bound is explicit — e.g.min(rel->max_hops, CYP_MAX_DEPTH)when> 0.Notes
Both are small and self-contained. Each wants a reproduce-first test: a deep / large-bound query that the clamp turns from "runs to graph size" into "capped at the ceiling". Landing these alongside (or ahead of) #883's expensive-expansion guard closes the untrusted-depth surface on the shared
cbm_store_bfspath.