rtk-ai/rtk

Security: rtk-rewrite.sh missing `--` terminator on `rtk rewrite "$CMD"` (line 55) enables flag-injection

Open

#1350 opened on Apr 16, 2026

View on GitHub
 (2 comments) (2 reactions) (0 assignees)Rust (48,085 stars) (2,914 forks)batch import
P1-criticalbugeffort-smallgood first issueresolved-pending-close

Description

Summary

In hooks/rtk-rewrite.sh (v3, line 55), the call REWRITTEN=$(rtk rewrite "$CMD" 2>/dev/null) does not use -- to signal end-of-options. If $CMD starts with - (e.g. a pathological LLM-generated command like --help), rtk rewrite interprets the command as its own CLI flag, emits help text with exit 0, and the hook feeds that help text back to Claude Code as the rewritten tool_input.command.

Claude Code's Bash tool then shell-executes the help text. The help text contains $(rtk rewrite "$CMD") in an example snippet, which bash will expand during execution.

Reproduction (rtk v0.36.0 on Linux x86_64 musl)

$ rtk --version
rtk 0.36.0

$ echo '{"tool_input":{"command":"--help"}}' | bash ~/.claude/hooks/rtk-rewrite.sh
{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","permissionDecisionReason":"RTK auto-rewrite","updatedInput":{"command":"Rewrite a raw command to its RTK equivalent (single source of truth for hooks)\n\n[...]\n\nUsed by Claude Code, Gemini CLI, and other LLM hooks: REWRITTEN=$(rtk rewrite \"$CMD\") || exit 0\n\n[...]"}}}

The updatedInput.command is now the help text — which CC's Bash tool will attempt to execute.

Impact

Severity: High (practical triggerability is low — Claude rarely emits ---prefixed commands — but the exploit path exists: any LLM-generated command starting with -/-- is transformed into arbitrary shell-interpretable text).

Fix

One-line change in hooks/rtk-rewrite.sh:

-REWRITTEN=$(rtk rewrite "$CMD" 2>/dev/null)
+REWRITTEN=$(rtk rewrite -- "$CMD" 2>/dev/null)

Also consider:

  • Verify rtk rewrite accepts -- as end-of-options marker (if not, add that to the CLI parser).
  • Add a defensive check in the hook that refuses to auto-allow when $REWRITTEN is empty or starts with non-command-looking text (e.g. looks like help output / contains newlines).

Workaround (users can apply today)

Wrap the hook command in settings.json with a case-guard that refuses any command starting with -:

"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command // empty' 2>/dev/null); case \"$CMD\" in -*) exit 0 ;; esac; echo \"$INPUT\" | /path/to/rtk-rewrite.sh"

This sanitizer approach has the downside that rtk init -g will overwrite settings.json on re-run, so users must use --no-patch on upgrades or live with manually re-applying the sanitizer.

Reported by

Found via Gemini 3.1 Pro second-opinion review during install on Claude Code v2.1.x. Happy to submit a PR if helpful.

Contributor guide