-
-
Notifications
You must be signed in to change notification settings - Fork 0
🐛 implement DecodeOptions.ListLimit handling to prevent DoS via memory exhaustion
#44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🐛 implement DecodeOptions.ListLimit handling to prevent DoS via memory exhaustion
#44
Conversation
📝 WalkthroughWalkthroughThis PR introduces overflow tracking for list limits in query string decoding. When values exceed ListLimit, lists are converted to index-keyed dictionaries with overflow state metadata. The decoder integrates new limit-aware merging logic, and test expectations are updated to reflect dictionary-based representations under constraint conditions. Changes
Sequence Diagram(s)sequenceDiagram
participant Decoder
participant CombineWithLimit
participant Merge
participant OverflowTable
Decoder->>CombineWithLimit: CombineWithLimit(existingVal, value, options)
activate CombineWithLimit
CombineWithLimit->>CombineWithLimit: Check if combined size exceeds ListLimit
alt Within Limit
CombineWithLimit->>Merge: Merge values normally
else Exceeds Limit
CombineWithLimit->>CombineWithLimit: ListToIndexMap(result to Dict)
CombineWithLimit->>OverflowTable: MarkOverflow(dict, maxIndex)
end
CombineWithLimit-->>Decoder: Return merged value (List or Dict with overflow)
deactivate CombineWithLimit
Decoder->>OverflowTable: Check IsOverflow(leaf)
alt Overflow Detected
Decoder-->>Decoder: Propagate leaf as-is
else No Overflow
Decoder->>CombineWithLimit: CombineWithLimit([], leaf, options)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
DecodeOptions.ListLimit handling to prevent DoS via memory exhaustion
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #44 +/- ##
==========================================
- Coverage 98.14% 97.40% -0.75%
==========================================
Files 15 15
Lines 1400 1500 +100
Branches 396 427 +31
==========================================
+ Hits 1374 1461 +87
- Misses 26 39 +13
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
QsNet.Tests/DecodeTests.cs (1)
2580-2590: Good coverage for implicit array limit overflow.The test correctly verifies that when the number of elements (105) exceeds
ListLimit(100), the result becomes anIDictionarywith all values preserved.💡 Optional: Consider using FluentAssertions' Which clause for cleaner assertions
result.Should().ContainKey("a"); -result["a"].Should().BeAssignableTo<IDictionary>(); -((IDictionary)result["a"]!).Count.Should().Be(105); +result["a"].Should().BeAssignableTo<IDictionary>() + .Which.Count.Should().Be(105);
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
QsNet.Comparison/js/package.jsonQsNet.Tests/DecodeTests.csQsNet.Tests/UtilsTests.csQsNet/Internal/Decoder.csQsNet/Internal/Utils.cs
🧰 Additional context used
📓 Path-based instructions (4)
QsNet.Tests/**/*.cs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
QsNet.Tests/**/*.cs: Tests must use xUnit with FluentAssertions
Test method naming should follow Should style
Files:
QsNet.Tests/UtilsTests.csQsNet.Tests/DecodeTests.cs
QsNet.Tests/**/DecodeTests.cs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
When extending decode tests, clone analogous existing cases in DecodeTests.cs to ensure js-qs parity
Files:
QsNet.Tests/DecodeTests.cs
QsNet/{Internal,Models,Enums,Constants}/**/*.cs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
All code under Internal/, Models/, Enums/, Constants/ is implementation detail and must not expose public surface
Files:
QsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
QsNet/**/*.cs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Add concise XML docs on new public members reflecting actual behavior for DocFX
Files:
QsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Models/DecodeOptions.cs : Preserve Depth, ParameterLimit, and ListLimit guards; keep defaults protective and make extensions feature-gated
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet.Tests/**/DecodeTests.cs : When extending decode tests, clone analogous existing cases in DecodeTests.cs to ensure js-qs parity
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Decoder*.cs : Escalate large indices to dictionary when index exceeds ListLimit
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet.Tests/**/EncodeTests.cs : When extending encode tests, clone analogous existing cases in EncodeTests.cs to ensure js-qs parity
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/Utils*.cs : Minimize allocations in Utils.Merge and Utils.Encode/Decode hot paths; avoid LINQ in tight loops
Applied to files:
QsNet.Tests/UtilsTests.csQsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/Utils*.cs : Utils.Compact and Utils.ToStringKeyDeepNonRecursive must avoid deep recursion; keep non-recursive approach
Applied to files:
QsNet.Tests/UtilsTests.csQsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet.Tests/**/*.cs : Tests must use xUnit with FluentAssertions
Applied to files:
QsNet.Tests/UtilsTests.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Models/DecodeOptions.cs : Preserve Depth, ParameterLimit, and ListLimit guards; keep defaults protective and make extensions feature-gated
Applied to files:
QsNet.Tests/UtilsTests.csQsNet.Tests/DecodeTests.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Decoder*.cs : Mixed list/dictionary notations must unify into a dictionary during decode
Applied to files:
QsNet.Tests/UtilsTests.csQsNet.Tests/DecodeTests.csQsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Decoder*.cs : Escalate large indices to dictionary when index exceeds ListLimit
Applied to files:
QsNet.Tests/UtilsTests.csQsNet.Tests/DecodeTests.csQsNet/Internal/Utils.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet.Tests/**/DecodeTests.cs : When extending decode tests, clone analogous existing cases in DecodeTests.cs to ensure js-qs parity
Applied to files:
QsNet.Tests/DecodeTests.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet.Tests/**/EncodeTests.cs : When extending encode tests, clone analogous existing cases in EncodeTests.cs to ensure js-qs parity
Applied to files:
QsNet.Tests/DecodeTests.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/{Encoder,Decoder}*.cs : Minimize allocations in hot paths; prefer reusing lists and avoid LINQ in tight loops unless existing code already does so
Applied to files:
QsNet.Tests/DecodeTests.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/{Encoder,Decoder}*.cs : Only encode dots in keys when EncodeDotInKeys is true; keep coherent with AllowDots/DecodeDotInKeys
Applied to files:
QsNet.Tests/DecodeTests.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to QsNet/**/*.cs : Add concise XML docs on new public members reflecting actual behavior for DocFX
Applied to files:
QsNet.Tests/DecodeTests.csQsNet/Internal/Utils.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/{Encoder,Decoder}*.cs : Honor strict null handling vs empty string differentiation
Applied to files:
QsNet.Tests/DecodeTests.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Encoder*.cs : Use Internal/Encoder.Encode for recursive encode producing key=value parts; keep branches small and composable using structured switch/pattern matching
Applied to files:
QsNet.Tests/DecodeTests.csQsNet/Internal/Decoder.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Encoder*.cs : Support comma list format round-tripping via CommaRoundTrip by appending [] when needed
Applied to files:
QsNet.Tests/DecodeTests.cs
📚 Learning: 2025-10-07T20:35:41.523Z
Learnt from: CR
Repo: techouse/qs-net PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-10-07T20:35:41.523Z
Learning: Applies to Internal/**/Decoder*.cs : Decoder.ParseKeys should drive key path parsing; keep logic small and composable
Applied to files:
QsNet/Internal/Decoder.cs
🧬 Code graph analysis (2)
QsNet.Tests/DecodeTests.cs (3)
QsNet/Internal/Decoder.cs (2)
Dictionary(107-271)List(581-698)QsNet/Qs.cs (2)
Dictionary(30-117)Qs(20-281)QsNet/Models/DecodeOptions.cs (4)
Decode(203-214)DecodeOptions(40-321)DecodeOptions(49-63)DecodeOptions(274-320)
QsNet/Internal/Decoder.cs (1)
QsNet/Internal/Utils.cs (4)
Utils(20-1397)CombineWithLimit(867-885)IsOverflow(813-816)List(783-811)
🔇 Additional comments (21)
QsNet/Internal/Utils.cs (6)
30-35: LGTM! Appropriate use of ConditionalWeakTable for overflow tracking.The
OverflowStateclass andOverflowTablecorrectly implement metadata tracking without preventing garbage collection. The sealed class and thread-safeConditionalWeakTableare appropriate design choices for this purpose.
834-856: LGTM! Robust numeric index detection.The round-trip check (
parsed.ToString(CultureInfo.InvariantCulture) == s) correctly rejects strings with leading zeros or whitespace (e.g., "007", " 7"), ensuring only canonical numeric representations are treated as array indices.
867-885: LGTM! Clean limit-aware combining logic.The implementation correctly:
- Bypasses limit checks when
ListLimit < 0- Appends to existing overflow dictionaries at the next index
- Converts to an index-keyed map when the limit is exceeded
The cast to
IDictionaryon line 874 is safe because only dictionaries created viaListToIndexMapare marked as overflow.
186-219: LGTM! Overflow-aware merging for target dictionaries.The overflow handling correctly:
- Propagates overflow state when copying to a mutable dictionary
- Appends source items at sequential indices starting from
targetMaxIndex + 1- Updates the overflow max index after processing
294-326: LGTM! Correct index shifting when merging overflow into primitives.When the source is an overflow dictionary and the target is a primitive, the implementation correctly:
- Places the primitive at index "0"
- Shifts all numeric source indices by +1
- Preserves non-numeric keys unchanged
- Adjusts the overflow max index to account for the shift
887-893: LGTM! Efficient list-to-map conversion.Pre-allocating dictionary capacity with
list.Countminimizes allocations in this hot path, per coding guidelines.QsNet/Internal/Decoder.cs (2)
256-256: LGTM! Correctly applies list limit when combining duplicate values.Using
CombineWithLimitensures theListLimitis enforced when merging duplicate key values, preventing unbounded list growth.
355-368: LGTM! Correct overflow propagation for array notation.The implementation properly handles the "[]" key case:
- Propagates overflow dictionaries as-is to preserve their state
- Uses
CombineWithLimitfor non-overflow leaves to enforce the limit- Preserves the empty-list special case for
AllowEmptyListsQsNet.Tests/UtilsTests.cs (5)
1236-1272: LGTM! Comprehensive boundary testing for list limits.The test effectively covers the three key scenarios: under limit (List), at limit (List), and over limit (Dictionary with overflow tracking). The boundary at
ListLimit = 3is well tested.
1274-1284: LGTM! Zero-limit edge case coverage.Correctly verifies that
ListLimit = 0immediately converts any list to an index-keyed dictionary, which is the expected behavior to prevent any list allocation.
1286-1307: LGTM! Validates overflow append semantics.Correctly verifies that appending to an overflow object:
- Mutates the same dictionary instance
- Uses the next sequential index
- Maintains overflow state
1309-1317: LGTM! Validates non-overflow map handling.Correctly verifies that a plain dictionary (not marked as overflow) is treated as a single value during combine, not as a container to append to. This distinction is crucial for correct merging semantics.
1319-1405: LGTM! Comprehensive merge behavior tests.The tests correctly cover:
- Overflow object merging with append semantics
- Plain map merging with value-as-key behavior
- Index shifting when merging overflow into primitives
- Respecting existing numeric keys when calculating next index
The test at line 1383-1405 validates the important edge case where the target has existing high numeric keys that must be respected when appending.
QsNet.Comparison/js/package.json (1)
8-8: Version 6.14.1 correctly addresses the high-severity DoS vulnerability (GHSA-6rw7-vpxm-498p / CVE-2025-15284).The fix enforces the
arrayLimitoption for bracket notation queries (e.g.,a[]=...), preventing memory exhaustion attacks via large query arrays. This change resolves the vulnerability.QsNet.Tests/DecodeTests.cs (7)
750-757: LGTM: Updated expectations for ListLimit=0 behavior.The test expectations correctly reflect the new behavior where lists with
ListLimit = 0are converted to dictionaries with string numeric keys ("0","1", etc.) instead of remaining asList<object?>.Also applies to: 777-784
2592-2627: LGTM: Well-structured boundary condition tests.The test correctly covers:
- At-limit case (3 elements, limit 3): remains a
List<object?>- Over-limit case (4 elements, limit 3): converts to
Dictionary- Edge case (2 elements, limit 1): converts to
DictionaryThis ensures proper escalation to dictionary when index exceeds ListLimit, as per the coding guidelines.
2629-2654: LGTM: Good coverage for duplicate value handling with limits.The test properly verifies that duplicate key values (
a=b&a=c&a=d) are:
- Combined into a
List<object?>when withinListLimit(20)- Converted to a
Dictionary<string, object?>with sequential numeric keys when exceedingListLimit(2)This validates the limit-aware merging logic introduced in the PR.
1290-1336: LGTM: Consistent updates for ListLimit=0 with StrictNullHandling.The test expectations correctly show that with
ListLimit = 0, arrays are represented as dictionaries with string numeric keys, properly preservingnullvalues and empty strings according toStrictNullHandlingsettings.
4271-4299: LGTM: Correct update for comma-split limit overflow behavior.The test properly reflects that when comma-split values exceed
ListLimitwithThrowOnLimitExceeded = false, the result converts to a dictionary while preserving all values. The comment on lines 4287-4288 accurately documents this behavior.
3000-3026: LGTM: Consistent with ListLimit=0 behavior.Test expectations correctly updated to reflect dictionary representation when
ListLimit = 0.
3509-3560: LGTM: Complete coverage for empty strings with ListLimit=0.The updated expectations for
ShouldAllowForEmptyStringsInArraysproperly test the dictionary-based representation with:
- Mixed
nulland empty string values- Correct sequential numeric keys
- Proper handling under
StrictNullHandling = true
DecodeOptions.ListLimit handling to prevent DoS via memory exhaustionDecodeOptions.ListLimit handling to prevent DoS via memory exhaustion
This pull request introduces significant changes to how decoded lists and arrays are represented and handled when certain limits are reached, as well as updates to the test suite and dependency versions. The main focus is on converting lists to dictionaries (maps) when a specified
ListLimitis exceeded, and on tracking this "overflow" state internally. The changes are thoroughly tested with new and updated unit tests.Key changes include:
Core logic and data structure changes
Decoder.csandUtils.csso that when a decoded list exceeds the configuredListLimit, it is converted from aList<object?>to aDictionary<object, object?>(map), and an internal overflow state is tracked using aConditionalWeakTable. This ensures consistent handling of oversized lists and allows for correct merging and combining of overflowed collections. [1] [2] [3] [4] [5]Test suite updates
Updated numerous test cases in
QsNet.Tests/DecodeTests.csto expect dictionaries (maps) instead of lists when theListLimitis exceeded or set to zero, ensuring the tests reflect the new overflow behavior. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]Added comprehensive new tests for overflow and limit behavior, including boundary conditions, implicit array limits, duplicate value handling, and correct merging/combining of overflowed collections. This ensures robust coverage of all edge cases related to list size limits. [1] [2]
Dependency update
qsJavaScript library dependency version from^6.14.0to^6.14.1inpackage.json.These changes improve the reliability and predictability of list handling in the decoder, especially in edge cases involving large or complex query strings.
Relates to GHSA-6rw7-vpxm-498p
Summary by CodeRabbit
Improvements
Chores
✏️ Tip: You can customize this high-level summary in your review settings.