Skip to content

fix(interp): gate $(<file) shortcut on cat being in the allowlist#192

Merged
matt-dz merged 6 commits intomainfrom
matt-dz/block-cat-shortcut-bypass
Apr 22, 2026
Merged

fix(interp): gate $(<file) shortcut on cat being in the allowlist#192
matt-dz merged 6 commits intomainfrom
matt-dz/block-cat-shortcut-bypass

Conversation

@matt-dz
Copy link
Copy Markdown
Collaborator

@matt-dz matt-dz commented Apr 22, 2026

Summary

  • The $(<file) POSIX shortcut in cmdSubst read files directly, skipping AllowedCommands. A caller with rshell:echo (no rshell:cat) could still read any file inside AllowedPaths via x=$(<file); echo "$x".
  • Per the security report's remediation: keep the shortcut functional, but gate it at runtime on cat being in AllowedCommands. The shortcut is conceptually $(cat file), so the allowlist check uses cat as its key.
  • When cat is missing from the allowlist, the shortcut prints $(<file): file read not permitted (cat not in allowed commands) to stderr, sets $?=1, and yields empty output — no file is opened.

Behaviour

# cat allowed → shortcut works
./rshell --allowed-paths /tmp --allow-all-commands -c 'x=$(<data.txt); echo "$x"'
hello from file

# cat NOT allowed → shortcut refuses, file contents not leaked
./rshell --allowed-paths /tmp --allowed-commands rshell:echo -c 'x=$(<data.txt); echo "[$x]"'
$(<file): file read not permitted (cat not in allowed commands)
[]

Test plan

  • make fmt clean
  • go test ./... green
  • Regression test TestCmdSubstCatShortcutCommandAllowlistBypass — the original exploit.
  • Happy-path test TestCmdSubstCatShortcutAllowedWhenCatAllowed — shortcut works when only cat (not all commands) is allowed.
  • Pentest suite in redir_input_bypass_pentest_test.go:
    • BlockedWhenCatNotAllowed — the exploit is refused
    • AllowedWhenCatAllowed — legitimate use works
    • ExitStatusPropagates$?=1 after a blocked shortcut
    • NoFileAccessWhenBlocked — gate short-circuits before any filesystem access
    • VariousContexts — shortcut in assignment / echo arg / backticks / for-iterator, each tested for both allowed and blocked cases
  • Scenario tests: 4 cat_shortcut*.yaml scenarios restored to their pre-exploit behaviour (shortcut works under default test harness which allows all commands), and cat_shortcut_bypass.yaml asserts the runtime gate refuses when cat is missing from allowed_commands.
  • Manually verified both paths with a local rshell build.
  • Bash comparison suite (RSHELL_BASH_TEST=1 go test ./tests/ -run TestShellScenariosAgainstBash) — cat_shortcut_bypass.yaml diverges from bash and is marked skip_assert_against_bash: true; the rest match bash.

matt-dz added 2 commits April 22, 2026 11:29
The $(<file) POSIX shortcut read files directly from cmdSubst, bypassing
AllowedCommands. Reject any < redirect that is not paired with a command
so the only way to read a file is through an allowed builtin
(e.g. $(cat file)).
The previous commit rejected bare < redirects but accepted any command
paired with <. That is broader than needed: `echo < file` would open a
file purely to hand its stdin to a command that ignores it. Narrow the
rule to the 11 file-reading builtins (cat, cut, grep, head, sed, sort,
strings, tail, tr, uniq, wc) so < only applies where reading is the
command's actual purpose. Also reject dynamic command names since they
cannot be validated statically.
@matt-dz matt-dz changed the title fix(interp): block $(<file) to prevent command allowlist bypass fix(interp): restrict < to file-reading builtins to plug $(<file) bypass Apr 22, 2026
Revert the overly-broad validation that blocked $(<file) entirely and
restricted < to a fixed set of commands. The report's remediation keeps
the shortcut functional but checks AllowedCommands at runtime: $(<file)
is treated as an implicit $(cat file), so the read only proceeds when
cat is allowed. This matches bash semantics and keeps legitimate uses
working while closing the original allowlist bypass.
@matt-dz matt-dz changed the title fix(interp): restrict < to file-reading builtins to plug $(<file) bypass fix(interp): gate $(<file) shortcut on cat being in the allowlist Apr 22, 2026
Add three regression tests to the existing pentest suite:

- SandboxEnforcedWhenCatAllowed: the gate and AllowedPaths are
  independent defences. Allowing cat does not let a caller read files
  outside the sandbox.
- SymlinkEscapeBlocked: a symlink inside AllowedPaths pointing outside
  must be refused by os.Root, not followed.
- VariableExpandedPath: the shortcut runs word expansion on its path,
  both for legitimate use (F=data.txt; $(<$F)) and to guarantee the
  gate cannot be dodged by hiding the path behind a variable.
@matt-dz
Copy link
Copy Markdown
Collaborator Author

matt-dz commented Apr 22, 2026

@codex conduct a comprehensive security and code review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4f45c641f3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/scenarios/shell/blocked_redirects/cat_shortcut_bypass.yaml Outdated
Per AGENTS.md, scenarios should prefer expect.stderr over
stderr_contains when the output is deterministic. The blocked
$(<file) gate emits a single fixed diagnostic and no variable
sandbox warnings, so the contains-only form would miss extra
stderr noise from a future regression.
@matt-dz
Copy link
Copy Markdown
Collaborator Author

matt-dz commented Apr 22, 2026

@codex conduct a comprehensive security and code review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@matt-dz matt-dz marked this pull request as ready for review April 22, 2026 17:49
Comment thread tests/scenarios/shell/blocked_redirects/cat_shortcut_bypass.yaml
Comment thread interp/runner_expand.go
…:cat is allowed

Pairs with blocked_redirects/cat_shortcut_bypass.yaml to prove the gate
decision hinges purely on cat's membership in allowed_commands and has
no other side effects on stdout/stderr.
@matt-dz matt-dz added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit 3f6e91b Apr 22, 2026
34 checks passed
@matt-dz matt-dz deleted the matt-dz/block-cat-shortcut-bypass branch April 22, 2026 17:56
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.

2 participants