fix(hooks): merge_guard hardening — bound ReDoS prefixes + output-side process-sub (#1001, #1002)#1003
Merged
michael-wojcik merged 14 commits intoJun 21, 2026
Conversation
…-qualified, tee-fanout (Synaptic-Labs-AI#1002)
…64 (defense-in-depth) (Synaptic-Labs-AI#1001)
…ualified, tee-fanout, metachar (Synaptic-Labs-AI#1002)
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.
Summary
Hardens the
merge_guardPreToolUse(Bash) security control by closing two pre-existing defects surfaced during the #933 / PR #1000 review. Both are scoped, INV-D2-aware fixes with non-vacuous regression proofs.What landed
F1 — bound O(n²) backtracking in global-flag prefixes (#1001) ·
45c406cf(?:\S+\s+)*in_GH_GLOBAL_FLAGS/_GIT_GLOBAL_FLAGS(shared/merge_guard_common.py) and the two inline httpie copies (merge_guard_pre.py) walk across shell separators and never self-terminate → O(n²) on a many-anchor witness. Bounded to(?:\S+\s+){0,32}via a new_MAX_GLOBAL_FLAG_TOKENSconstant. Empirically linear on both independently-quadratic call sites (is_dangerous_command+detect_command_operation_type).F2 — catch output-side process-substitution under-block (#1002) ·
1d921b4c_has_process_substitution_to_shellmatched only input-sidebash <(...), missing the output-side… > >(bash)form. For the echo/printf carriers (the only true stdout vectors),echo "<danger>" > >(bash)was a real under-block. Added an output-side arm, kept pre-local (post has no execution-gating strip surface — no merge_guard_pre: compound-detector false-positives 2>&1 + classification asymmetry + single-use token forces re-approval on retries #720 asymmetry).tee/cat/2>excluded).Tests ·
be4de4ae(#1001 perf),247c4d22(#1002 output-side)test_merge_guard_perf.py: scaling-ratio + generous absolute ceiling on both quadratic paths + the httpie copy.test_merge_guard_output_side_process_sub.py: 39 cases (output-side positives with dangerous payloads, stderr/non-shell/plain-file exclusions, input-side parity).Version ·
d9ed2379— PATCH bump to 4.4.34 (hardening, not a new user-facing capability).Why
merge_guardis a security control governed by INV-D2: never weaken detection of a real executing destructive op; over-block is acceptable, under-block is a hole. F2 closes a real under-block; F1 removes a DoS without changing the match set within the realistic envelope.Known residual (documented, accepted)
F1's
{0,32}bound carries a narrow, threat-model-justified residual under-block: a command with >32 valid global tokens (e.g.git -c k=v× 17 = 34 tokens,push --force) executes yet is missed. Accepted as a documented tradeoff vs the O(n²) DoS under the operator/LLM-authored threat model (not adversarial network input; self-padding to evade one's own guard is self-defeating)._MAX_GLOBAL_FLAG_TOKENSis raisable to 64 (still O(1)) if deemed material.Closes #1001
Closes #1002