ReDoS-safe @pattern: linear-time regex engine for constraint validation - #39
Merged
Merged
Conversation
Generated @pattern validation evaluated patterns with std::regex, a backtracking engine, so a catastrophic pattern like ^([0-9]+)+$ plus a request-sized non-matching input could hang the dispatch thread. - smithy::Regex (smithy/core/regex.h): a Thompson-NFA engine simulated breadth-first (Pike VM), O(pattern x input) for every pattern/input combination. Covers the ECMA-262 subset Smithy patterns use over UTF-8 bytes with std::regex_search partial-match semantics: literals, '.', character classes, class escapes, \xHH/\uHHHH, ^ $ \b \B, greedy and lazy quantifiers including {n,m}, groups, and alternation. Compiled program size, repeat counts, and group nesting are capped; Outcome-based errors per ADR-0003. Zero new dependencies. - ValidationGenerator emits smithy::Regex instead of std::regex, failing closed if a pattern were ever uncompilable at runtime, and rejects backreferences and lookaround (inherently backtracking constructs) at generation time with an error naming the pattern and the fix. - Tests: engine unit suite including linearity checks on classic ReDoS bombs and a differential test against std::regex on random inputs; a regex fuzz harness (smoke-tested in every CI job); jsonrpc2 authored conformance suite grows @pattern coverage with JsonRpc2PatternMismatch and JsonRpc2PatternReDoSInput (the catastrophic pattern answered promptly); codegen unit test pins the generation-time rejection. - Regenerated fixture output; stale ReDoS notes in the exclusion list and server-guide replaced with the new contract; runtime.md lists the engine. Closes #38 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyQAo21Pv6GYhHrkbQj8xQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes #38 (broken out of the post-0.1.0 tracking issue #29): generated
@patternvalidation evaluated patterns withstd::regex, a backtracking engine, so a catastrophic pattern like^([0-9]+)+$against a request-sized non-matching input could hang the server's dispatch thread (ReDoS). No C++ standard fixes this —std::regexhas no complexity guarantee in C++20/23 and libstdc++ backtracks exponentially.smithy::Regex(smithy/core/regex.h, new in//runtime:core): a Thompson-NFA engine simulated breadth-first (Pike VM),O(pattern × input)for every pattern/input combination. Covers the ECMA-262 subset Smithy patterns use, over UTF-8 bytes withstd::regex_searchpartial-match semantics: literals,., character classes (ranges/negation/class escapes),\d \w \s&co,\xHH/\uHHHH,^ $ \b \B, greedy+lazy quantifiers including{n,m}, groups (capturing/non-capturing/named), and alternation. Program size, repeat counts, and group nesting are capped;Outcome-based errors per ADR-0003. Zero new dependencies — hand-rolled per the owner's call after weighing RE2 (BCR) which would pull abseil-cpp into every consumer build.ValidationGeneratornow emitssmithy::Regex(fail-closed if a pattern were ever uncompilable at runtime) and rejects backreferences and lookaround at generation time — inherently backtracking constructs — with an error naming the pattern and the fix.@patterncoverage —JsonRpc2PatternMismatch(suite-exactValidationExceptionmessage with the pattern text) andJsonRpc2PatternReDoSInput(the catastrophic pattern must answer promptly; under the old engine this test hangs).std::regexon random inputs), aregexfuzz harness (smoke-tested in every CI job, libFuzzer target under--config=fuzz), and a codegen unit test pinning the generation-time backreference rejection.Testing
bazel test //...(minus benchmarks/Beast targets, whose archives this sandbox's proxy can't fetch — the documented exclusion): 64/64 pass, including the newcore_testregex suite,regex_fuzz_smoke, and the regenerated jsonrpc2 malformed suite with the ReDoS case.(cd codegen && gradle build spotlessCheck)green, including the newrejectsBacktrackingOnlyPatternsplugin test.gradle generateFixtures generateProtocolTestsoutput committed (weather + roundtrip/rest servers switch tosmithy::Regex; jsonrpc2 regenerates with the new cases); regeneration is byte-identical for everything else.Checklist
bazel test //...and(cd codegen && gradle build spotlessCheck)pass locally (Beast/benchmark targets excluded per docs — proxy blocks their archive fetches)🤖 Generated with Claude Code
https://claude.ai/code/session_01SyQAo21Pv6GYhHrkbQj8xQ
Generated by Claude Code