Skip to content

feat(rules): support recursive wrapper command evaluation#45

Merged
fohte merged 6 commits intomainfrom
fohte/impl-runok-init-wrapper-eval
Feb 18, 2026
Merged

feat(rules): support recursive wrapper command evaluation#45
fohte merged 6 commits intomainfrom
fohte/impl-runok-init-wrapper-eval

Conversation

@fohte
Copy link
Owner

@fohte fohte commented Feb 18, 2026

Why

  • Wrapper commands like sudo rm -rf / or bash -c "curl -X POST ..." need their inner commands evaluated against rules
    • Duplicating every rule for each wrapper combination causes combinatorial explosion

What

  • Extract the <cmd> placeholder argument from wrapper patterns defined in definitions.wrappers (e.g., sudo <cmd>, bash -c <cmd>) and recursively evaluate the inner command against rules
  • Enforce a recursion depth limit (default 10), returning RuleError::RecursionDepthExceeded on overflow
  • Aggregate direct rule results with wrapper evaluation results using Explicit Deny Wins

Open with Devin

Commands like `sudo rm -rf /` or `bash -c "curl -X POST ..."` need to
be evaluated by unwrapping the wrapper and checking the inner command
against rules. Without this, users would need to duplicate every rule
for each wrapper combination, causing combinatorial explosion.

Add `extract_placeholder` to pattern_matcher for capturing tokens at
`<cmd>` placeholder positions. Integrate wrapper unwrapping into
`evaluate_command` via depth-tracked recursion (max 10), merging the
inner result with direct rule matches using Explicit Deny Wins.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist
Copy link

Summary of Changes

Hello @fohte, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the rule engine's capability by introducing recursive evaluation for commands wrapped by common utilities like sudo or bash -c. This change eliminates the need for users to define redundant rules for every possible wrapper combination, streamlining rule management and improving the accuracy of security policy enforcement by ensuring that the actual command being executed is always evaluated against the defined rules, even when nested.

Highlights

  • Recursive Wrapper Command Evaluation: The rule engine now supports recursively evaluating inner commands within wrapper patterns defined in definitions.wrappers, such as sudo <cmd> or bash -c <cmd>.
  • Recursion Depth Limit: A maximum recursion depth of 10 has been enforced for wrapper command evaluation to prevent infinite loops, returning a RuleError::RecursionDepthExceeded on overflow.
  • Explicit Deny Wins Aggregation: Evaluation results from direct rule matches and recursive wrapper evaluations are aggregated using an 'Explicit Deny Wins' strategy, ensuring the most restrictive action is applied.
Changelog
  • src/rules/pattern_matcher.rs
    • Introduced extract_placeholder function to match wrapper patterns and extract the inner command string.
    • Implemented extract_placeholder_inner for recursive token matching and placeholder capture, including a step limit.
  • src/rules/rule_engine.rs
    • Updated imports to include extract_placeholder for new wrapper logic.
    • Defined MAX_WRAPPER_DEPTH to prevent infinite recursion in wrapper evaluation.
    • Refactored evaluate_command into evaluate_command_inner to manage recursion depth.
    • Integrated wrapper command evaluation into the main rule engine flow.
    • Added try_unwrap_wrapper function to detect and recursively evaluate commands within defined wrapper patterns.
    • Implemented merge_results and action_priority to apply 'Explicit Deny Wins' when combining direct and wrapper evaluation results.
    • Ensured RuleError::RecursionDepthExceeded is returned if the wrapper recursion limit is hit.
    • Expanded unit tests to cover recursive wrapper command evaluation, including sudo, bash -c, nested wrappers, and recursion depth overflow scenarios.
Activity
  • No human activity (comments, reviews) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

…king

Compound commands inside wrappers (e.g., `bash -c 'ls; rm -rf /'`)
were evaluated as a single command string, allowing dangerous
sub-commands to bypass rule checks. Backtracking in the placeholder
extractor also corrupted the captured token list.

Split extracted inner commands via `extract_commands` so each
sub-command is evaluated individually. Add `captured.truncate()` on
backtracking to prevent stale tokens from leaking into results.
Only capture tokens for `<cmd>` placeholders, not other placeholder
names. Parameterize similar wrapper tests per project conventions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

fohte and others added 3 commits February 18, 2026 12:59
Wrapper patterns containing Optional ([...]) or PathRef (<path:name>)
tokens were silently falling back to basic matching, which could hide
configuration mistakes from users. Now these cases return an explicit
`UnsupportedWrapperToken` error instead of silently swallowing the issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`extract_placeholder_inner` only tried consuming exactly one command
token when `<cmd>` was not the final pattern token. This caused
multi-token inner commands to fail extraction for patterns like
`wrapper <cmd> --flag`. Now it tries consuming 1, 2, ... tokens with
backtracking, matching the Wildcard strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
`extract_placeholder` joined captured tokens with a bare space, losing
quoting information for tokens that contained spaces (e.g.,
`sudo echo 'hello world'` became `echo hello world`). Now
`extract_placeholder` returns `Vec<String>` instead of a joined string,
and `try_unwrap_wrapper` applies `shell_quote_join` for multi-token
captures (structured commands like `sudo <cmd>`) while passing
single-token captures as-is (shell scripts like `bash -c <cmd>`).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

When a non-`<cmd>` placeholder (e.g., `<user>`) was the last token in a
wrapper pattern, it incorrectly matched any number of remaining command
tokens. Now it consumes exactly one token, consistent with
`match_tokens_inner`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fohte fohte merged commit c6fd568 into main Feb 18, 2026
3 checks passed
@fohte fohte deleted the fohte/impl-runok-init-wrapper-eval branch February 18, 2026 14:37
@fohte-bot fohte-bot bot mentioned this pull request Feb 18, 2026
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.

1 participant