Skip to content

refactor(approval): replace regex inline-script-execution detection w… - #60801

Closed
MorAlekss wants to merge 2 commits into
NousResearch:mainfrom
MorAlekss:refactor/tokenizer-inline-script-detection
Closed

refactor(approval): replace regex inline-script-execution detection w…#60801
MorAlekss wants to merge 2 commits into
NousResearch:mainfrom
MorAlekss:refactor/tokenizer-inline-script-detection

Conversation

@MorAlekss

Copy link
Copy Markdown
Contributor

Summary

Replaces regex-based inline-script-execution detection in
tools/approval.py (python/node/perl/ruby/php -c/-e/--eval/-p/--print,
PowerShell -File/-Command, and heredoc) with a tokenizer-based
implementation (shlex.split() + a language-specific exec-flag table).

This follows from #57666 and #57990, which received three and two
rounds of comments respectively, each finding a new command shape the
regex missed: combined/glued/long-form flags, versioned interpreter
binaries, flags with their own argument (-W ignore), and an entirely
different flag letter for the same behavior (node -p/--print, neither
'e' nor 'c'). Matching flag characters in a flat string can't reliably
model argument parsing; that needs an actual tokenizer, not another
pattern, so each round closed one shape while leaving the general
problem open for the next.

This isn't cosmetic. ~/.hermes/config.yaml is where approvals.mode,
yolo, and the permanent allowlist live, so a missed flag shape lets a
command rewrite it with no approval at all, silently disabling the
agent's own approval gate. Tokenizing once (the same shlex.split()
approach already used in this file's _literal_command_substitution_output()
and in hermes_cli/mcp_security.py's _command_basename()) closes the
whole class of gaps instead of the one shape found so far.

#57666 and #57990 are left open rather than closed, since both contain
other, independent fixes that don't touch this code path. If this PR
merges, their own -e/-c/-File regex rules become the same fragile
pattern this one replaces; the natural next step is rebasing those
additions onto this tokenizer instead of carrying their own regex
forward.


Root cause

The old rules were regular expressions trying to answer an argument-
parsing question ("skip past any number of other flags, some of which
take their own argument, in short or long form, and find one meaning
'run this code inline'"). A flat-string pattern has no way to know
which flags consume a following token without hand-enumerating every
one, and no amount of added alternation closes that gap in general; it
only closes the specific shapes someone tested. This is not a fourth
incremental fix to the same rule; it is a different category of tool
for a problem regex was never suited to solve, closing every case in
this class at once rather than the next one someone happens to test.


Behavioral change

Before: detection depended on which flag-combination shapes had been
added to the regex so far.

After: _detect_inline_script_execution() tokenizes the command,
identifies the interpreter by family (version-suffix tolerant), and
scans every token against that family's known inline-exec flags,
independent of how many other flags precede it or how the target flag
is glued/combined.


What changed

tools/approval.py:

  • Added _INTERPRETER_FAMILY_PATTERN, _EXEC_FLAGS,
    _interpreter_family(), _detect_inline_script_execution(),
    _inline_exec_result(), called as a fallback in
    detect_dangerous_command() after the existing regex loop
  • Removed the -e/-c and heredoc regex rules this replaces
  • Added _PATTERN_KEY_ALIASES entries mapping the new pattern_keys to
    the old regex description strings, so an operator's existing
    permanent allowlist entry isn't silently invalidated
  • Ordering fix: an exec flag and heredoc syntax in the same command
    now report the exec flag, not heredoc

tests/tools/test_approval.py:

  • New TestTokenizerInlineScriptExecution class (31 tests) covering
    every previously-missed shape from both PRs, node/ruby -c/--check
    (must stay unflagged), PowerShell -File/-Command including the
    false-positive-attribution case, versioned binaries, malformed shlex
    input, and the aliasing fix
  • Updated two existing tests whose expectations relied on gaps this
    fix closes

What is NOT changed

  • GitHub/GitLab/Svix-style detection elsewhere is unrelated and
    untouched
  • perl/ruby -i (in-place file edit) rules are untouched, a
    different threat category not part of this refactor's scope
  • #57666 and #57990 are left open and unmodified; see Summary
  • All existing approval tests pass (329 passed)

@alt-glitch alt-glitch added type/refactor Code restructuring, no behavior change tool/terminal Terminal execution and process management comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have labels Jul 8, 2026
@MorAlekss

Copy link
Copy Markdown
Contributor Author

Added recognition for py/py.exe (the Windows Python launcher, bundled with the official installer since Python 3.3) as part of the python interpreter family. It wasn't covered by the old regex either, so this isn't a regression, just filling in a gap the tokenizer refactor made easy to close (one alternative in _INTERPRETER_FAMILY_PATTERN). Verified pypy doesn't get confused with the py alias, and added regression tests for both.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 10, 2026
@MorAlekss

Copy link
Copy Markdown
Contributor Author

Superseded by and merged as part of #63585 by @teknium1, which unifies this tokenizer direction with the execution-option coverage from #59899 into a single, broader detector (also covers rg/sort/ag/man hooks, -- semantics, and nested payload detection). Closing this one since its content now lives there.

@MorAlekss MorAlekss closed this Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/terminal Terminal execution and process management type/refactor Code restructuring, no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants