fix(rest_over_grpc): bound query field path nesting depth - #729
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The depth cap is enforced at the query-tree construction point with clear error behavior and targeted tests validating both sides of the bound.
Pull request overview
This PR hardens rest_over_grpc query-parameter decoding by bounding the nesting depth created from dotted query keys (e.g., ?a.b.c=1), preventing untrusted inputs from driving unbounded recursive descent during query tree construction and deserialization.
Changes:
- Introduces a
MAX_QUERY_FIELD_DEPTHconstant (64) and enforces it during dotted query key insertion. - Returns a structured
InvalidArgumenterror when the depth is exceeded instead of recursing further. - Adds unit tests covering both the rejection path (including via
decode_request) and the “at bound” acceptance behavior; updates the crate changelog.
File summaries
| File | Description |
|---|---|
| crates/rest_over_grpc/src/transcode/overlay.rs | Adds a max query-field nesting depth guard to query-tree insertion and tests the bound through internal and public decode paths. |
| crates/rest_over_grpc/CHANGELOG.md | Documents the new nesting-depth limit under Unreleased fixes. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #729 +/- ##
=======================================
Coverage 100.0% 100.0%
=======================================
Files 583 583
Lines 62891 62909 +18
=======================================
+ Hits 62891 62909 +18
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I would expect the limitation get documented in README |
There was a problem hiding this comment.
🔵 Needs a closer look
It introduces a depth bound but still accepts empty path segments from dotted keys (e.g. a..b/.a), and the updated test module should follow the repo’s coverage exclusion convention for mod tests.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
crates/rest_over_grpc/src/transcode/overlay.rs:488
key.split('.')yields empty segments for leading/trailing dots or consecutive dots (e.g. "a..b" or ".a"), butinsert_query_nodecurrently accepts an empty segment and inserts a""field name into the query tree. It’s safer to reject empty segments as invalid request structure to avoid confusing/ambiguous field paths.
let key = path
.next()
.ok_or_else(|| TranscodeError::structure("query parameter has an empty field path"))?;
let has_more = path.clone().next().is_some();
crates/rest_over_grpc/src/transcode/overlay.rs:605
- Test modules in this repo are typically excluded from coverage measurement with
#[cfg_attr(coverage_nightly, coverage(off))]above#[cfg(test)] mod tests(e.g. crates/tick/src/error.rs:103-105). Adding new tests here without that attribute can cause test-only lines to count toward the coverage gate.
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Thanks — both points are noted, and both are declined for this PR. Answering here because they arrived as suppressed comments in review 5112095065 rather than as threads. Empty path segments (
The depth cap itself remains fixed and deliberately not configurable, for the reason given under "Design note" in the PR description. |
Dotted query keys built one nesting level per `.`-separated segment with no bound, so the build and the matching deserialization both descended once per segment. Cap the depth at 64 levels and reject a deeper path with `Code::InvalidArgument`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…known limitation Add "recurse" to the repo word list so cargo-spellcheck accepts the sentence that explains why the query field path depth is bounded, and state plainly on MAX_QUERY_FIELD_DEPTH that an over-deep path is rejected rather than truncated, and that the cap is deliberately fixed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cap was documented only on the private MAX_QUERY_FIELD_DEPTH constant in a private module, so it never reached rustdoc or the README. State it in the crate-level Limitations section, where a user of the crate can see it, and regenerate the README paragraph to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
9b05ae6 to
5389189
Compare
There was a problem hiding this comment.
🟢 Approval recommended
The depth bound is applied at the recursion driver, maps to a clear InvalidArgument structure error, and is covered by targeted tests and documentation updates.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
anvil-readme-check regenerates the README with cargo-doc2readme pinned to =0.6.4 and compares the input hash recorded in the dependencies_info blob. The previous commit added the prose by hand, leaving the stale hash, so CI reported "Input has changed". Regenerated with 0.6.4; only the blob moves. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The depth bound is enforced at the query-tree construction point, is exercised through the public decode entry point, and the documentation matches the behavioral change.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.
Summary
A dotted query parameter key (
?a.b.c=1) built one nesting level per.-separated segment, and both that build and the matching deserialization descended once per level. Neither had an upper bound, so the nesting depth was limited only by the input.This caps the depth at 64 levels. A deeper field path is now rejected with
Code::InvalidArgumentinstead of being decoded.Effects
Validation
just anvil-fmt,just anvil-clippycargo testforrest_over_grpcandrest_over_grpc_testsdecode_requestentry point.Design note
The 64-level cap is deliberately fixed: it is not configurable, not runtime-tunable, and not exposed in any public API, because a security bound a caller can raise does not bound anything. The cap is documented on
MAX_QUERY_FIELD_DEPTHas a known limitation — field paths nested deeper than it are rejected with a clear error, never silently truncated and never allowed to overflow the stack. Making the depth configurable is out of scope for this fix.The limit is documented for crate users in the crate-level
# Limitationssection oflib.rs(and the generatedREADME.md), becauseMAX_QUERY_FIELD_DEPTHis private andcodegen_helpersis#[doc(hidden)], so a doc comment on either would never reach rustdoc.