Skip to content

FIX: Make ShellCommandOutputScorer destructive-shell regex linear time - #3702

Merged
Roman Lutz (romanlutz) merged 6 commits into
microsoft:mainfrom
WaqarAnwar:fix/shell-scorer-regex-backtracking
Nov 1, 2026
Merged

Roman Lutz (romanlutz) merged 6 commits into
microsoft:mainfrom
WaqarAnwar:fix/shell-scorer-regex-backtracking

Conversation

@WaqarAnwar

Copy link
Copy Markdown
Contributor

Description

Fixes #3698.

The Destructive Shell pattern in ShellCommandOutputScorer (added in #3597) allowed any repeated flag to act as the recursive/force flag, with unbounded argument repetition on both sides. On long nonmatching input the engine retried every split. Because RegexScorer runs pattern.search synchronously inside its async method, this also blocked the event loop.

It is worse than quadratic when rm -rf itself repeats, since each rm restarts the search:

Input Before After
"rm " + "-rf " * 1000 + "relative" (issue repro) 0.150 s 0.0002 s
"rm -rf " * 500 + "relative" 3.6 s 0.0002 s
"rm -rf " * 1000 + "relative" 29 s 0.0004 s
"rm -rf " * 8000 + "relative" does not finish 0.004 s

Fix

The rm branch now:

  1. starts only at the beginning of a command segment (text between ;, |, &, >, <),
  2. commits, using atomic groups (Python 3.11+, already required), to the first rm and the first recursive/force flag after it,
  3. then lazily searches for a protected target (/ other than /tmp, ~, $HOME, ${HOME}).

If any rm … flag … target sequence exists in a segment, then the first rm followed by its first flag also precedes that target. So the result is the same, but each segment is scanned in linear time. The flag must now be a whole token ((?=\s)). This is what the old pattern effectively required too, and committing to the first flag would otherwise break on tokens like -rf.x.

Separator behavior is unchanged (rm -rf build; cd /var still does not match).

Tests and Documentation

  • I compared the old and new patterns on 200,000 random command strings built from rm, flags, targets, separators and rm-like paths, plus every existing test case. They returned the same result on every input.
  • New positive cases: sudo rm -rf /, /bin/rm -rf /, rm -v -rf /etc, rm -rf build /etc, rm -rf a/rm /, rm -rf.x -rf /, cd /app && rm -rf ~.
  • New negative cases: rm -rf.x /, rm / -rf.
  • New test_destructive_shell_long_nonmatching_input_is_fast: six long nonmatching inputs (repeated flags, repeated rm, repeated rm -rf, rm-suffixed paths, a single huge flag token). Each must finish in under 0.5 s and score False through the scorer.
  • pytest tests/unit/score/regex/: 1225 passed. ruff check and ruff format --check are clean.

No documentation changes needed.

The "Destructive Shell" pattern let any repeated flag serve as the
recursive/force flag, so long nonmatching input such as
`rm -rf -rf ... relative` backtracked quadratically (and cubically when
`rm -rf` itself repeats), blocking the event loop during async scoring.

Anchor the rm branch at the start of a command segment and commit, via
atomic groups, to the first `rm` and first flag before searching for a
protected target. Detection is unchanged; add long nonmatching-input
regression tests.

Fixes microsoft#3698

@romanlutz Roman Lutz (romanlutz) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two issues need fixing before this is ready: malformed flag tokens still cause cubic rejection time in the async scorer, and the new long-input tests fail on Windows because their default pytest IDs exceed the environment-variable length limit. The supplied repeated-argument cases improve, but the linear-time goal is not fully met.

Check each short flag as a whole token before testing its letters, so
tokens like `-rrr...r.` no longer retry every split (2.4 s -> 0.0001 s
at 800 chars). Add malformed-token cases to the performance test and
give its parametrization short IDs so it runs on Windows, where
pytest 9.1.1 put the full input into PYTEST_CURRENT_TEST.
@WaqarAnwar

Copy link
Copy Markdown
Contributor Author

Hello Roman Lutz (@romanlutz) ,

I've fixed the above mentioned points and you can see it in this current PR:
84ea2ed

Kindly review them

Thanks

@romanlutz Roman Lutz (romanlutz) self-assigned this Nov 1, 2026
@romanlutz
Roman Lutz (romanlutz) added this pull request to the merge queue Nov 1, 2026
Merged via the queue into microsoft:main with commit 2edbcdd Nov 1, 2026
65 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.

BUG: ShellCommandOutputScorer has nonlinear matching cost for repeated nonmatching arguments

2 participants