Summary
Two catastrophic-backtracking (ReDoS) pattern shapes slip through _heuristic_unsafe() in libs/core/genblaze_core/providers/pattern_safety.py regardless of whether google-re2 is installed. These are pre-existing gaps in the heuristic itself, distinct from #148 (which made the heuristic always run) and #80 (the original ReDoS guard). Surfaced by the adversarial reviewer during the #148 fix (PR #154) and independently reproduced.
Bypassing shapes (verified)
Both pass assert_safe() today, then backtrack exponentially when matched by the stdlib re engine used at runtime (family.py ModelFamily.matches()):
- Non-identical alternation —
(a|aa)+$
The duplicate-alternation-branch check only rejects identical branches; overlapping-but-not-identical branches (a vs aa) still produce exponential backtracking.
- Delimiter-separated adjacent quantified groups —
(a+)-?(a+)-?(a+)-?(a+)$
The adjacent-unbounded-quantified-groups check looks for directly adjacent groups; inserting an optional delimiter (-?) between them evades the adjacency detection while preserving the exponential blowup.
Evidence
Reproduced independently by the reviewer: real exponential blowup, e.g. n=32 adversarial input → ~0.31s and climbing (vs. sub-millisecond for benign input), both accepted by assert_safe() on main after PR #154.
Impact
Same class/severity as #80/#148: the shipped connector catalog is clean, so no first-party exploit today. Exposure is a third-party/future connector pattern of one of these shapes passing the only guard, where step.model is attacker-influenceable at preflight → worker hang (DoS). Priority P2 — forward-looking guard-strength gap, no live exploit.
Suggested fix
Extend _heuristic_unsafe() to cover:
- alternation branches with a shared prefix / overlapping language (not just byte-identical branches), and
- adjacent unbounded-quantified groups separated by an optional/nullable connector (
-?, \s*, (?:...)?, etc.), by treating a nullable separator as adjacency.
Add regression tests asserting both shapes are rejected by assert_safe() in both the _HAS_RE2=True and _HAS_RE2=False branches (mirroring the parametrization added in PR #154). Consider noting in the module docstring that the heuristic is best-effort and google-re2 at runtime would be the only complete guarantee (out of scope here).
References
Summary
Two catastrophic-backtracking (ReDoS) pattern shapes slip through
_heuristic_unsafe()inlibs/core/genblaze_core/providers/pattern_safety.pyregardless of whethergoogle-re2is installed. These are pre-existing gaps in the heuristic itself, distinct from #148 (which made the heuristic always run) and #80 (the original ReDoS guard). Surfaced by the adversarial reviewer during the #148 fix (PR #154) and independently reproduced.Bypassing shapes (verified)
Both pass
assert_safe()today, then backtrack exponentially when matched by the stdlibreengine used at runtime (family.pyModelFamily.matches()):(a|aa)+$The duplicate-alternation-branch check only rejects identical branches; overlapping-but-not-identical branches (
avsaa) still produce exponential backtracking.(a+)-?(a+)-?(a+)-?(a+)$The adjacent-unbounded-quantified-groups check looks for directly adjacent groups; inserting an optional delimiter (
-?) between them evades the adjacency detection while preserving the exponential blowup.Evidence
Reproduced independently by the reviewer: real exponential blowup, e.g.
n=32adversarial input → ~0.31s and climbing (vs. sub-millisecond for benign input), both accepted byassert_safe()onmainafter PR #154.Impact
Same class/severity as #80/#148: the shipped connector catalog is clean, so no first-party exploit today. Exposure is a third-party/future connector pattern of one of these shapes passing the only guard, where
step.modelis attacker-influenceable at preflight → worker hang (DoS). Priority P2 — forward-looking guard-strength gap, no live exploit.Suggested fix
Extend
_heuristic_unsafe()to cover:-?,\s*,(?:...)?, etc.), by treating a nullable separator as adjacency.Add regression tests asserting both shapes are rejected by
assert_safe()in both the_HAS_RE2=Trueand_HAS_RE2=Falsebranches (mirroring the parametrization added in PR #154). Consider noting in the module docstring that the heuristic is best-effort andgoogle-re2at runtime would be the only complete guarantee (out of scope here).References
libs/core/genblaze_core/providers/pattern_safety.py(_heuristic_unsafeand its sub-checks),providers/family.py(matches()— stdlibreat runtime)