Skip to content

Cursor runner: constrained Bash(...) rules are blocked at preToolUse Shell gate #1423

Open

Description

@kolli-habee

Summary

When using the Cursor runner with constrained Bash permissions such as Bash(git:*) or Bash(npm:*), the session is blocked at Cursor's preToolUse hook before the command-level permission can be evaluated.

This appears inconsistent with the documented Cyrus allowedTools semantics, where patterns like Bash(git:*) are shown as the intended way to allow only Git commands.

Reproduction

Self-hosted Cyrus, Cursor runner, repository config:

{
  "allowedTools": [
    "Read",
    "Edit",
    "Write",
    "Bash(git:*)",
    "Bash(npm:*)",
    "Skill"
  ]
}

Trigger a GitHub-driven Cursor session whose first shell command is for example:

git branch --show-current && git status

Observed failure:

Cyrus blocked: no allow rule matched
(event=preToolUse, candidates=[{"kind":"Tool","value":"Shell"}])

The command never reaches the beforeShellExecution gate where the constrained Bash(git:*) rule could be evaluated.

Expected behavior

Bash(git:*) should allow the Shell tool at the generic preToolUse stage, while still restricting the concrete command at beforeShellExecution to matching Git commands only.

Conceptually:

preToolUse:
  Tool(Shell) -> allow because at least one Bash(...) rule exists

beforeShellExecution:
  git status -> allow
  npm run lint -> allow if Bash(npm:*) exists
  rm -rf ... -> deny

Current implementation detail

In packages/cursor-runner/src/permissions.ts, a bare Bash maps to:

Tool(Shell)
Shell(*)

but a constrained rule such as Bash(git:*) maps only to command-level patterns such as:

Shell(git)
Shell(git:*)

so it does not satisfy the earlier preToolUse candidate Tool(Shell).

This is reproducible with the current Cursor permission helper and matches the runtime error above.

Why this matters

Adding bare Bash is not an acceptable workaround for a least-privilege setup, because it also expands to Shell(*), effectively allowing unrestricted shell commands.

The documented configuration examples include constrained rules such as:

["Read", "Edit", "Bash(git:*)", "Task"]

so constrained Bash permissions should work with the Cursor runner without requiring unrestricted Bash.

Suggested direction

For constrained Bash(...) / Shell(...) rules, consider also emitting Tool(Shell) for the generic preToolUse gate, while keeping the command-level allow list constrained.

There may also be value in reviewing whether emitting both Shell(git) and Shell(git:*) is intentionally broad, since the helper currently allows when any candidate matches.

Happy to provide additional logs or test cases if useful.

Activity

  1. kolli-habee commented on Aug 22, 2026

    @kolli-habee
    Author

    Additional finding after fixing the preToolUse gate:

    The constrained Shell pattern still fails for commands whose arguments contain /, for example:

    git add docs/CYRUS_OPERATIONS.md
    git push origin docs/issue-25-cyrus-operations-readme

    Root cause:
    The permission helper uses the same glob matcher for file paths and Shell patterns. * is translated to [^/]*, which is appropriate for path-style matching but means Shell(git:*) does not match command arguments containing /.

    Local fix:

    • keep existing path semantics for Read/Write patterns
    • for Shell patterns, let * match / as well
    • additionally reject obvious shell composition (&&, ;, |, redirects, backticks, $() so a constrained Shell(git:*) rule does not trivially become a general shell escape

    Verification:

    • cursor-runner tests: 52/52 passed
    • real GitHub/Cyrus/Cursor TASK RUN successfully completed git add, git commit, and git push

    Local commit:
    98b33513 fix(cursor): allow slashes in constrained shell args safely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions