Skip to content

bash: recursive file-reading commands (grep -r, rg, ls -R) bypass filename-pattern deny rules #56545

Closed as not planned

Description

@devfpittelo

Summary

permission.bash deny rules are matched against the command string. Recursive file-reading commands (grep -r <pattern> <dir>, rg <pattern> <dir>, ls -R <dir>, find <dir> -type f) only name the directory in their command text, so they read and return the full contents of files whose names match deny patterns (e.g. *.env*, *.pem*, *.key*) without any rule matching.

A direct cat .env is correctly denied. grep -r "API_KEY" . returns the contents of .env — allowed, because no deny pattern matches the command string.

This is the bash-tool sibling of the grep/glob tool gap tracked (and auto-closed as stale) in #17073, with duplicates #35503 / #35684, and related to the wrapper-matching gap in #54008.

Reproduction (synthetic files, no real secrets)

  1. Configure:
    "permission": {
      "bash": {
        "*": "allow",
        "*.env*": "deny",
        "*.key*": "deny"
      }
    }
  2. Create secrets/fake-credentials.env and secrets/server.key (synthetic content).
  3. cat secrets/fake-credentials.env → denied ✔ (command string matches *.env*).
  4. grep -r "SYNTHETIC" secrets/ → allowed, returns the full contents of both files ✘ — the command string (grep -r "SYNTHETIC" secrets/) matches no deny pattern, yet the operation reads deny-matched files.

Verified 2026-10-17 on a live session (files created out-of-band via the file-write tool, then read by an allowed recursive grep).

Expected behavior

Either:

  • (a) deny rules are also evaluated against the files a command will actually read (requires intercepting recursive reads — heavy), or
  • (b) a documented, supported post-execution redaction/filter hook exists so configurations can strip deny-matched files' content from tool output before the model sees it (the approach proven by the community plugin opencode-env-protect and requested in [FEATURE]: Protect .env files in grep/glob results, not just direct read #17073), or
  • (c) the limitation is documented explicitly next to the bash permission docs: filename-pattern denies are command-string matches only and do NOT protect file contents reached via recursive reads.

Actual behavior

Recursive reads silently bypass filename-pattern deny rules; denied-file contents enter the model context.

Security impact

Configurations that rely on *.env* / *.key* / *.pem* bash denies as a secret-exfiltration guard have a false sense of protection: an agent (or a prompt-injected agent) can exfiltrate secret file contents with a single allowed recursive grep, never naming the file.

Suggested fix direction

Post-execution output filtering keyed on the same permission patterns (option b) is the smallest change that closes the class: after a bash command completes, scan its output for content sourced from files matching deny patterns and redact/filter. This also covers #17073's grep/glob tool variant with one mechanism.

Environment

  • OpenCode: current release channel (verified 2026-10-17)
  • OS: Linux (Arch, bash)

Activity

  1. opencode-agent commented on Oct 17, 2026

    @opencode-agent
    Contributor

    Thanks for the clear write-up and the safe repro with fake files.

    To restate the request: filename-based deny rules (*.env*, *.key*, …) should also protect a file's contents when a shell command reads it without naming it, for example grep -r, rg or find. That could be done by checking the files a command actually reads, by a supported way to redact them from output after the command finishes, or at least by documenting the limit next to the shell permission docs.

    Some context: shell permission rules match the parsed command text. They decide which commands can run, not which files those commands touch. A recursive grep is one of many ways around a filename deny (python -c, xargs cat, globs, a script the model writes), so these rules are a guardrail, not a sandbox, and shouldn't be the only thing protecting secrets. We agree the docs could say this much more plainly. Maintainers will consider the request; I can't promise anything about whether or when it happens.

    In the meantime, on v2 (@opencode/cli) a plugin can rewrite tool output through the execute.after tool hook, which is how plugins like opencode-env-protect work. One known limit is that shell output streamed while the command runs is saved before that hook runs (#51937).

    Related issues:

  2. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Closing as working as intended. Shell permission rules match the command text the scanner produces, not the files a command ends up reading. A filename deny like *.env* was never a sandbox for the shell tool: grep -r, rg, find -exec cat, python -c or a generated script can all read those files. The permissions docs note that shell runs with the host user's filesystem, process and network authority, and recommend a narrow shell allowlist.

    If you need content-level protection, use a narrow shell allowlist instead of *: allow plus filename denies, or run OpenCode in a sandbox or container that cannot see the secrets.

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