rtk-ai / rtk-ai/rtk

Permission verdict is computed on a command that is not the one that runs — a model-authored `rtk` prefix escapes the host allowlist

Open
#3,970 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:cli area:security bug priority:critical
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 by test_run_already_rtk_returns_some — makes the identity answer correct
    for rtk 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.