Repository navigation
fix(analyzer): end an expansion word at its enclosing double quote - #780
elliottwaves-20 wants to merge 6 commits into
Conversation
yashrajp22
left a comment
There was a problem hiding this comment.
One completeness regression is confirmed in both source and freshly installed wheels. The 111 selected HEAD tests pass in each mode, but the focused comment-ownership checks reproduce the issue below. Verification covered 80 CLI scans across BASE/HEAD and source/wheel modes; Greptile returned no findings on the narrower authored diff.
| if owned_word_positions is not None: | ||
| owned_word_positions.add(cursor) | ||
| if quoted_expansion_starts is not None: | ||
| quoted_expansion_starts.update(pending_quoted_expansions) |
There was a problem hiding this comment.
Could we keep these positions local to the candidate and commit them only after the full word parses successfully and passes the comment/assignment checks? A referenced run.sh containing:
# "prefix $(date)
$CMD"" -rf /leaves $CMD in the shared set when the parse starting in the comment later returns None. Its independent scan then treats the first quote as an enclosing delimiter and skips the destructive operands, even though the empty quotes belong to the real command (CMD could be rm). In both source and fresh-wheel scans, BASE retains static_parse_limit and AE1 with safe_to_install=false; HEAD reports complete inspection, drops AE1, and returns safe_to_install=true. Please discard quote ownership from failed/comment parses and cover this concatenated-quote case in a regression test.
`echo "x: $(date)"` followed by more than 4 KB of text, and the PowerShell
form `"Stale path: $($cfg.command)"`, were recorded as
`static_parse_limit`. The `$(` inside the string is its own command-word
candidate, but only a quote directly before it (`"$(date)"`) told the word
parser where the word ends. Otherwise the word ran past `)` and the
string's closing quote opened a new quote that reached the end of the file.
The word parser now reports the expansions (`$(...)`, `${...}`/`$NAME`,
backticks) that sit directly inside a double-quoted span, and only once
that span's closing quote is reached. A candidate at such a position is
parsed with that quote as its enclosing delimiter, the mechanism already
used for assignment values, and its operand scan stops at the same quote.
Commands nested inside a substitution, unclosed strings and runtime output
joined to an escape keep today's results.
Refs NVIDIA#694
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: elliottwaves-20 <pail1217@web.de>
…arse A parse that starts inside a comment can close a double quote on the next line and record the real command there as quote-enclosed. When that parse was later discarded or confined to the comment, the record stayed, and the command's own operand scan stopped at its first quote, so `$CMD"" -rf /` was reported as completely inspected. Collect the positions per candidate and commit them only under the gate that already protects owned word positions. Reported-by: yashrajp22 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: elliottwaves-20 <pail1217@web.de>
A double-quoted span that crosses a line may pair a heredoc, comment or prose quote with a later command line. Pin that such a command keeps its operands under inspection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: elliottwaves-20 <pail1217@web.de>
The leak was not limited to comments: any candidate parse that closed a double quote and was then discarded left its quoted-expansion starts behind. Pin a plain code case that fails without the previous fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: elliottwaves-20 <pail1217@web.de>
A successful parse can still pair a quote from a heredoc body or Markdown prose with the empty quotes of a later command line when another quote follows, for example a trailing `# "` comment. It then recorded that command as quote-enclosed, and the command's `-rf /` operands were never inspected. Only a double-quoted span that stays on one line now ends the word of an expansion inside it; multiline spans keep the conservative partial result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: elliottwaves-20 <pail1217@web.de>
…spans CommonMark ends a line at a lone CR as well, so a Markdown file with CR line endings could still pair a prose quote with a later command line. A single-line span now contains neither LF nor CR; the CR of a CRLF line ending lies outside such a span, so single-line strings stay complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: elliottwaves-20 <pail1217@web.de>
|
Thanks @yashrajp22. I confirmed the regression, and while fixing it I found a second path to the same bypass. Root cause.
In both cases the real Fix.
Tests in
All of these pass on main. All except the completed-comment-parse pin fail on the previous head. Rebase. The branch is rebased onto current main, including #778. The only conflict was the call into Verification against main 3c8e4b9:
Disclosure: an existing bypass on main, not caused by this PR. main already reports these inputs as completely inspected, with no TM1. A quote in a comment or heredoc body pairs with the empty quotes of a later real command, and no expansion is involved: The same applies to My fuzzing found one input of this class that main reports as partial only incidentally. An unrelated empty 'cat <<E\n"$(date)$()""\nE\n$CMD"" -rf / # "\n'This branch reports it as complete, as main does for the simpler variants above. A second fuzz hit was built the same way, with a comment ending in a backslash before the heredoc terminator. Closing the class needs heredoc- and comment-aware quote pairing in the generic word scan, which is outside the scope of this PR. I am happy to open a separate issue with these reproducers. |
a243e9e to
9fca6bb
Compare
Summary
These inputs make
has_bounded_parse_exhaustion(static_patterns_tool_misuse) reportstatic_parse_limitonce more than 4 KB of text follows:echo "Started: $(date)";Write-Warning "Stale path: $($cfg.command)".The file is then recorded as partially inspected, and every
SKILL.mdreference to it becomes anAE1. Refs #694 (the PowerShell interpolation case reported there; the POSIX form shows it is a general shell-parser issue).Root cause
_has_shell_command_word_exhaustionchecks every command-word candidate, including the$(inside the string. The word parser only knows where such a word ends when the quote is directly before it ("$(date)", via_command_wrapper_quote). With text in between, the word continues past). The string's closing"then opens a new quote that runs to the end of the file, and the unresolved span exceeds_SHELL_COMMAND_WORD_CHARS."x: $(date) y"was already complete only because the space ends the word first.Fix
_parse_shell_command_wordaccepts an optionalquoted_expansion_startsset. It records the start of each$(...),${...}/$NAMEand backtick expansion that sits directly inside a double-quoted span. Positions are committed only when that span's closing quote is reached, and are dropped when a parameter expansion consumes the quote (inherited_quote_closed)._has_shell_command_word_exhaustionshares one set across its candidates. It parses a candidate at a recorded position withenclosing_delimiter='"', the mechanism already used for assignment values, so the word ends at the string's own closing quote. The runtime-command operand scan (_bounded_shell_tokens) receives the same boundary through a new optionalenclosing_delimiterargument. Without it, a short script such asecho "x: $(date)"followed byrm -rf /tmp/buildwould newly become partial, because the operand scan would reopen the closing quote.Unchanged (stay partial):
"x: $($TOOL -rf /)","x: `$TOOL -rf /`"and"x: $(echo "a b"; $TOOL -rf /)"."x: $(printf %s r m) -rf /").x; "$CMD $(date)" -rf /) andsh -cstrings."Path: $($HOME)\bin". That word is alreadylimitedonmainthrough the command-reconstruction check, and this PR does not touch that check.analyze(). For example,echo "x: $(rm -rf /)"keeps TM1.Tests
tests/nodes/analyzers/test_double_quoted_expansion_word.pyhas 28 tests. 11 of them fail onmain, and all controls pass on both.${A:-$(date)}default, a nested quoted argument"x: $(echo "a b")", the PowerShell member and$env:forms, and a/binsuffix.${HOME}, the adjacent quote, and a trailing word.echo "x: $(date)"followed byrm -rf /tmp/build,; rm -rf ./buildorrm -rf "$HOME"/cache.echo "a $(x)"; $(printf r)"$X" -rf /.run.shhelper is COMPLETED.main.ruff checkandruff format --checkare clean."...: $($manifest.servers.$n.command)","...: $($cfg.activeProfiles -join ', ')"and"...: $($_.Exception.Message)". With this change the trigger disappears. Over 4000 files from about 200 public skills, no file goes from complete to partial, and the TM1 count is unchanged.main: I ran about 3000 targeted fragments (quoted expansions, mispaired quotes, comments, heredocs, assignments, followed by runtime commands andrm) and 3000 random ones.mainin its equivalent plain form, such as$(printf rm) -rf /(where the TM1 finding is kept), or its "command" is inert: it lives in a comment, an assignment value or an argument, or it contains a space ort:and therefore cannot namerm.; $CMD -rf /follows, for examplea="…$CMD"; $CMD -rf /".mainreported these as complete._has_runtime_command_operand_exhaustion. The resolution passesenclosing_delimiterthrough that helper. I tested it applied on top of fix(analyzer): keep Python and Perl syntax from exhausting the shell parser #778: 1004 tests across this PR, fix(analyzer): treat a command wrapper with no command as complete #779, fix(analyzer): keep Python and Perl syntax from exhausting the shell parser #778 andtest_security_reconstruction.pypass.Residual
Other host-syntax families from #694 remain. In PowerShell, the backtick is an escape character (
"`"$(...)`""). They also include JavaScript template literals, and Rust character literals and comment apostrophes:itoa1.0.18src/lib.rsis still partial onmain. They need host-language ownership in the style of #778 and are not addressed here.🤖 Generated with Claude Code