Skip to content

fix: cap client-driven traversal depth (var-length Cypher + MCP tools)#888

Merged
DeusData merged 1 commit into
mainfrom
distill/887-traversal-depth-caps
Jul 5, 2026
Merged

fix: cap client-driven traversal depth (var-length Cypher + MCP tools)#888
DeusData merged 1 commit into
mainfrom
distill/887-traversal-depth-caps

Conversation

@DeusData

@DeusData DeusData commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Closes #887.

What

Two graph-traversal entry points let input drive an effectively unbounded BFS depth:

  1. Explicit variable-length Cypher bounds weren't capped. expand_var_length used
    rel->max_hops > 0 ? rel->max_hops : CYP_MAX_DEPTH, so the ceiling (10) applied only
    to the unbounded forms (*, *n.., *..m). An explicit [:CALLS*1..1000000] passed its
    bound straight into cbm_store_bfs — an unbounded traversal (on a cyclic call graph the
    recursive CTE emits up to |V| × depth rows).
  2. trace_call_path / detect_changes depth was unclamped. MCP_MAX_DEPTH = 15 was
    defined but never applied; the client depth argument flowed straight into the shared
    cbm_store_bfs.

Fix

  • Two env-configurable ceilings in limits.c, mirroring cbm_max_file_bytes:
    cbm_cypher_max_depth() (CBM_CYPHER_MAX_DEPTH, default 10) and
    cbm_mcp_max_depth() (CBM_MCP_MAX_DEPTH, default 15).
  • Both entry points clamp to the ceiling and WARN on clamp (cypher.depth_capped /
    mcp.depth_capped) — never a silent truncation, per the project's limits philosophy.
  • Removed the now-dead CYP_MAX_DEPTH / MCP_MAX_DEPTH constants (the ceilings live in
    limits.c).

Reproduce-first

  • cypher_exec_var_length_explicit_bound_capped — a *1..12 walk over a 13-node chain.
    RED = 12 rows (uncapped); GREEN = 10 (capped at the ceiling; N11/N12 never emitted).
  • tool_trace_call_path_depth_clampeddepth:1000 over an 18-node chain. RED = the
    response reaches n16; GREEN = the walk stops at hop 15 (n15 present, n16 absent).

Both fail on the pre-fix code and pass only with the clamp; full suite green, lint clean.

Notes

Companion to #883 (variable-length path semantics, still in review): that change rewrites
cbm_store_bfs to enumerate simple paths, which would amplify these unbounded-depth walks —
bounding the depth here closes the surface regardless of that PR's outcome.

Explicit variable-length Cypher bounds ([:CALLS*1..N]) and the client
`depth` on trace_call_path / detect_changes were passed straight into
cbm_store_bfs, so an arbitrarily large value drove an unbounded traversal
(a DoS on cyclic graphs). The MCP_MAX_DEPTH ceiling was defined but never
applied, and CYP_MAX_DEPTH capped only the unbounded var-length forms.

Add env-configurable ceilings cbm_cypher_max_depth() (CBM_CYPHER_MAX_DEPTH,
default 10) and cbm_mcp_max_depth() (CBM_MCP_MAX_DEPTH, default 15), clamp
both entry points to them, and WARN on clamp -- never a silent truncation.
Remove the now-dead CYP_MAX_DEPTH / MCP_MAX_DEPTH constants.

Reproduce-first: cypher_exec_var_length_explicit_bound_capped (12 -> 10 rows)
and tool_trace_call_path_depth_clamped (depth 1000 -> hop 15).

Closes #887.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData DeusData enabled auto-merge July 5, 2026 14:14
@DeusData DeusData merged commit 0488acf into main Jul 5, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Clamp client-controlled traversal depth (trace_call_path + explicit var-length *1..N)

1 participant