Skip to content

ReDoS-safe @pattern: linear-time regex engine for constraint validation - #39

Merged
aaylward merged 1 commit into
mainfrom
claude/issue-27-fix-42z1s0
Jul 8, 2026
Merged

aaylward merged 1 commit into
mainfrom
claude/issue-27-fix-42z1s0

Conversation

@aaylward

@aaylward aaylward commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

What

Closes #38 (broken out of the post-0.1.0 tracking issue #29): generated @pattern validation evaluated patterns with std::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::regex has 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 with std::regex_search partial-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.
  • ValidationGenerator now emits smithy::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.
  • Conformance: the authored jsonrpc2 suite grows @pattern coverage — JsonRpc2PatternMismatch (suite-exact ValidationException message with the pattern text) and JsonRpc2PatternReDoSInput (the catastrophic pattern must answer promptly; under the old engine this test hangs).
  • Tests: engine unit suite (fixture patterns, anchors/classes/quantifiers/groups/boundaries, linearity checks on classic ReDoS bombs at 50k–100k input bytes, and a differential test against std::regex on random inputs), a regex fuzz harness (smoke-tested in every CI job, libFuzzer target under --config=fuzz), and a codegen unit test pinning the generation-time backreference rejection.
  • Stale ReDoS notes removed: the exclusion-list comment and the server-guide "not yet generated" entry are replaced by the documented contract (server-guide Constraint-validation section, runtime.md module table).

Testing

  • bazel test //... (minus benchmarks/Beast targets, whose archives this sandbox's proxy can't fetch — the documented exclusion): 64/64 pass, including the new core_test regex suite, regex_fuzz_smoke, and the regenerated jsonrpc2 malformed suite with the ReDoS case.
  • (cd codegen && gradle build spotlessCheck) green, including the new rejectsBacktrackingOnlyPatterns plugin test.
  • gradle generateFixtures generateProtocolTests output committed (weather + roundtrip/rest servers switch to smithy::Regex; jsonrpc2 regenerates with the new cases); regeneration is byte-identical for everything else.
  • clang-format and clang-tidy clean on the new runtime files; buildifier clean on the BUILD edits.

Checklist

  • Tests added/updated for the change (engine unit tests, fuzz harness, conformance cases, codegen rejection test)
  • bazel test //... and (cd codegen && gradle build spotlessCheck) pass locally (Beast/benchmark targets excluded per docs — proxy blocks their archive fetches)
  • Formatting clean (clang-format, clang-tidy, buildifier, spotless)
  • Architectural decisions recorded as an ADR (not applicable — implements the documented Outcome/error conventions; no new dependency or interface contract beyond one runtime class)

🤖 Generated with Claude Code

https://claude.ai/code/session_01SyQAo21Pv6GYhHrkbQj8xQ


Generated by Claude Code

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
@aaylward
aaylward merged commit 574fc6d into main Jul 8, 2026
12 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.

ReDoS-safe @pattern: linear-time regex evaluation in generated constraint validation

2 participants