Skip to content

fix(index,core): add depth-cap guards against unbounded recursion DoS#6595

Merged
bug-ops merged 1 commit into
mainfrom
fix/6551-recursion-depth-guard
Jul 20, 2026
Merged

fix(index,core): add depth-cap guards against unbounded recursion DoS#6595
bug-ops merged 1 commit into
mainfrom
fix/6551-recursion-depth-guard

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Summary

Two independent uncontrolled-recursion stack-overflow DoS bugs (CWE-674), same defect class and fix pattern, fixed together:

  • chunk_children (crates/zeph-index/src/chunker.rs) recursed into AST child nodes with no depth limit — an attacker-crafted deeply-nested source file placed in a watched/indexed project directory could exhaust the native call stack and abort the whole process.
  • collect_strings (crates/zeph-core/src/agent/tool_execution/tool_call_dag.rs) recursed over a tool call's input JSON with no depth limit — a deeply nested LLM-generated tool-call argument (e.g. steered by prompt-injected content the model read) could do the same on a more directly reachable surface.

A Rust stack overflow aborts the process uncatchably — it cannot be caught by catch_unwind, Result, or any async task boundary — so both are genuine full-DoS vectors, not just failures of the triggering task.

Fix

Both functions now take an explicit depth: usize parameter bounded by a module-level const:

  • MAX_CHUNK_DEPTH = 512 — at the bound, chunk_children emits the oversized subtree as a single leaf chunk instead of recursing further (no content lost, just coarser chunk granularity for that subtree).
  • MAX_JSON_DEPTH = 256 — at the bound, collect_strings stops descending into that branch (dependency detection just misses strings past the bound rather than crashing).

Neither path panics when the bound is hit; both log a tracing::warn! so operators get a signal that adversarial nesting was encountered in production.

Test plan

  • 11 new regression tests (4 chunker, 7 DAG walker): depth-boundary exactness, graceful degradation under attacker-scale nesting (2000 levels / 612-level real tree-sitter-parseable fixture), and that nesting depth (not element count/width) is what's bounded — a 10,000-element flat array/object is unaffected.
  • cargo +nightly fmt --check — clean
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings — clean
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins — 14909 passed, 36 skipped, no regressions
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler" — clean
  • gitleaks protect --staged — no leaks

Follow-up

Filed #6594 to track the same unguarded-recursion pattern in two sibling functions (zeph-sanitizer/src/exfiltration.rs, zeph-tools/src/verifier.rs) that are out of scope for this PR — currently bounded in practice by serde_json's own default recursion limit on the upstream parse, but latent risk if that ever changes.

Closes #6551
Closes #6554

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate bug Something isn't working size/L Large PR (201-500 lines) labels Jul 20, 2026
chunk_children (zeph-index chunker) and collect_strings (zeph-core
tool_call_dag) recursed on the native call stack with no depth limit,
letting adversarially deep input (a crafted source file, or a deeply
nested LLM tool-call JSON payload) exhaust the stack and abort the
process. Both now cap recursion depth and degrade gracefully instead
of crashing, logging a warning when the bound is hit.

Closes #6551
Closes #6554
@bug-ops
bug-ops force-pushed the fix/6551-recursion-depth-guard branch from 87fc13d to d33879b Compare July 20, 2026 16:53
@bug-ops
bug-ops enabled auto-merge (squash) July 20, 2026 16:53
@bug-ops
bug-ops merged commit 9522ec7 into main Jul 20, 2026
43 checks passed
@bug-ops
bug-ops deleted the fix/6551-recursion-depth-guard branch July 20, 2026 17:02
bug-ops added a commit that referenced this pull request Jul 20, 2026
…fix guest-mode escaping (#6604)

Guest-mode responses (TelegramChannel::flush_chunks) now format through
the same markdown_to_telegram renderer used by regular messages and send
with parse_mode="MarkdownV2" instead of an unconverted "HTML" call, which
previously either broke Telegram's HTML parser or rendered literal
Markdown to guest users.

TelegramRenderer now prefixes every line of a multi-line blockquote with
">" (previously only the first line was quoted), and nested blockquotes
flatten to a single level instead of accumulating an unescaped ">>",
which Telegram's MarkdownV2 grammar rejects outright (400 can't parse
entities, dropping the whole message). A nesting-depth cap
(MAX_BLOCKQUOTE_NESTING_DEPTH = 512, same pattern as MAX_CHUNK_DEPTH from
#6595) bounds tracked-mark memory for adversarially deep input.

Adds TelegramConfig.expandable_blockquote_min_lines (default 10):
blockquotes at or above the threshold render as Bot API 10.1's
expandable (collapsed-by-default) form; 0 disables it unconditionally.
Wired into --init and --migrate-config.

Also wires TelegramConfig.guest_mode/.bot_to_bot into create_channel —
these fields were previously accepted but never threaded into the
TelegramChannel builder, so Guest Mode and Bot-to-Bot were unreachable
in a running agent even with both enabled. Fixes a latent crash this
exposed: the Guest Mode proxy's TcpListener was never set non-blocking
before handing it to tokio, which panics on recent tokio versions.

Closes #6541
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/L Large PR (201-500 lines)

Projects

None yet

1 participant