Permission verdict is computed on a command that is not the one that runs — a model-authored `rtk` prefix escapes the host allowlist
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 81.1k
- Forks
- 5.1k
- Avg merge
- 4d 21h
- Merged PRs (30d)
- 35
Description
Summary
The permission verdict a hook reports is computed on a command that is not the one that
ends up running. Two independent instances, same root cause: decide_from_verdict(cmd, permissions::check_command_for(cmd, host)) (src/hooks/hook_cmd.rs:274) checks the raw
input, while what executes is decided elsewhere.
| host checks | actually runs | how they diverge | |
|---|---|---|---|
| 1. rtk swallows the inner command | rtk rm file |
rm file, as an rtk subprocess |
the host never sees rm; if rtk is allowed, rm is never checked |
| 2. the rewrite hands back a different command | rtk source x |
source x, stamped "allow" |
only reachable if a rewrite drops the rtk prefix |
Instance 1 was reported by @rmsc on #2508. Instance 2 is not currently reachable on
develop — it is what made #2534's prefix-stripping approach unmergeable, and it is
recorded here so the constraint is known before anyone tries again.
Instance 1 — reproduction
$ rtk rewrite "rm file"
# exit 1 — passthrough; the host checks `rm` against its allowlist
$ rtk rewrite "rtk rm file"
rtk rm file # exit 3 — "handled"; if `rtk` is allowed, `rm` is never checked
The same holds for every non-routed command: rtk curl …, rtk chmod …. A model that
prefixes commands itself — which the full awareness level instructs it to do, see #3969 —
therefore routes around the host's per-command rules.
Bash(rtk:*) in permissions.allow is the natural workaround for prefixed-command prompt
spam, which makes this the common configuration rather than an unusual one.
Instance 2 — reproduction (with #2534 applied)
command_matches_pattern (src/hooks/permissions.rs:406) is a word-boundary prefix match,
so Bash(source:*) does not match rtk source x. With
permissions: {"allow": ["Bash(rtk:*)"], "deny": ["Bash(source:*)"]}:
$ echo '{"tool_name":"Bash","tool_input":{"command":"rtk source /tmp/evil.sh"}}' | rtk hook claude
# develop: (no output — Defer; the host applies its own rules to `rtk source …`)
# with #2534: {"updatedInput":{"command":"source /tmp/evil.sh"},"permissionDecision":"allow"}
An explicitly denied command, emitted with an explicit auto-allow. With no allow rule the
verdict degrades to Ask, but the deny is still bypassed. Compound forms are protected by the
#1213 per-segment rule; this is the single-segment case.
Why the obvious fixes do not work
- Gate on exit 3. A model may legitimately write
rtk ls -la, and #241's contract —
still pinned bytest_run_already_rtk_returns_some— makes the identity answer correct
forrtk rewrite. - Strip the prefix unconditionally. Wrong for
rtk gain,rtk discover,rtk proxy,
and for every command RTK actually filters. - Docs alone. #3969 will cut down how often agents prefix commands, but a host cannot
rely on the model's cooperation for an allowlist.
What the host needs is which command the rewrite ultimately runs, which the current
stdout/exit protocol does not express. Any fix that changes the emitted command must also
recompute the verdict on it, and take the stricter of the two.
Related
- #2508 — where @rmsc reported instance 1
- #2534 — rejected; instance 2 is why
- #3969 — the guidance that makes agents prefix commands in the first place
- #3955 — consolidates both hook decision paths behind one
decide; the natural place for
the verdict to be recomputed on the emitted command
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/hooks/hook_cmd.rs:274 and src/hooks/permissions.rs:406, then read the unified decision path from #3955 and the rtk rewrite behavior covered by test_run_already_rtk_returns_some. The fix is complete when the host evaluates the command that ultimately runs, preserves the stricter verdict, and covers both prefixed and rewritten-command cases without breaking the existing rtk identity behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- authorization, cli, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100