Back to skill

Security audit

Dispatchi (Ralph Loop)

Security checks for vulnerabilities and agentic risk

Overview

The skill is purpose-aligned, but it needs review because it launches unattended coding agents with unsafe path handling and automatic approval of security prompts.

Install only if you are comfortable with this skill launching local Claude Code sessions in the background. Before use, restrict project and task names to safe identifiers, keep ENABLE_CALLBACK off unless external sharing is intended, avoid bypassPermissions or similar modes, and run it under a low-privilege account with access only to approved repositories and result directories.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/run_dispatchi.sh:161
Finding

Shell Command Injection Through the tmux Output Pipeline

Content
View full analysis
> '$RESULT_DIR/task-output.txt'" || true ``` ### Technical Analysis The user-controlled `PROJECT` and `TASK_NAME` arguments are embedded in `RUN_ID` and `RESULT_DIR`. The resulting path is then interpolated into the command string supplied to `tmux pipe-pane`. Although the path is surrounded by literal single quotes, neither argument is validated and embedded single-quote characters are not escaped. A single quote in either argument can terminate the intended shell quoting and append arbitrary shell syntax. `tmux pipe-pane` executes the supplied command through a shell. Consequently, this is not merely an unusual filename issue: attacker-controlled shell metacharacters become executable syntax when tmux starts the output pipeline. For example, a task name containing a value conceptually similar to: ```text x'; touch /tmp/dispatchi-pwned; # ``` can cause the generated command to contain: ```bash cat >> '/configured/result/path/...-x'; touch /tmp/dispatchi-pwned; #-interactive/task-output.txt' ``` The exact prefix depends on the configured result directory and generated timestamp, but the single quote closes the path argument, the semicolon begins a new command, and the comment marker suppresses the remaining generated text. ### Attack Path 1. An attacker obtains the ability to invoke `/dispatchi` or influence its `projec ...[truncated 1195 chars]
Remediation
View remediation
&2 exit 2 fi if [[ ! "$TASK_NAME" =~ $IDENTIFIER_RE ]]; then echo "Error: invalid task identifier" >&2 exit 2 fi ``` 2. Avoid passing user-derived values through a shell command. Prefer a logging mechanism that accepts an argument vector rather than a command string. 3. If `pipe-pane` necessarily requires shell syntax, quote the complete output path with a proven shell-escaping operation instead of manually adding single quotes: ```bash printf -v OUTPUT_Q '%q' "$RESULT_DIR/task-output.txt" tmux -S "$TMUX_SOCKET_PATH" pipe-pane -o \ -t "${TMUX_SESSION}:0.0" \ "cat >> $OUTPUT_Q" ``` Strict identifier validation should still be retained as defense in depth. 4. Add regression tests containing single quotes, semicolons, command substitutions, newlines, spaces, and comment characters. 5. Run the dispatcher under a dedicated low-privilege account with minimal filesystem and credential access to reduce the impact of any future command-injection flaw. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/run_dispatchi.sh:81
Finding

Path Traversal Through Unvalidated Project and Task Identifiers

Content
View full analysis
"$RESULT_DIR/task-meta.json" : > "$RESULT_DIR/task-output.txt" ``` ### Technical Analysis The script treats `PROJECT` and `TASK_NAME` as path components without validating them or checking the canonical paths against their intended roots. A project value containing absolute-path or traversal components such as `../` can cause: ```bash WORKDIR="${REPOS_ROOT}/${PROJECT}" ``` to resolve outside `REPOS_ROOT`. The same value is used beneath `RESULTS_BASE`, while both project and task values are included in `RUN_ID`. Slash and traversal components can therefore affect the result and log directory hierarchy as well. Shell quoting prevents whitespace splitting at these locations, but it does not prevent filesystem path traversal. The subsequent `mkdir -p` and redirections operate on the resol ...[truncated 1978 chars]
Remediation
View remediation
&2 exit 2 ;; esac ``` Apply equivalent checks to result and log paths. 3. Do not include raw user input in directory or log filenames. Generate an opaque random run identifier and store the human-readable project/task names only inside metadata: ```bash RUN_ID="$(date -u +%Y%m%d-%H%M%S)-$(openssl rand -hex 8)" ``` 4. Defend against symbolic-link traversal. Use protected, non-user-writable base directories, inspect path components, and create output files using APIs or flags that reject symbolic links and accidental overwrites where practical. 5. Confirm that the requested project already exists in an explicit project allowlist rather than automatically creating any requested workspace. 6. Add tests for `../`, nested separators, absolute paths, repeated slashes, symbolic links, control characters, and excessively long identifiers. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/vendor/claude_code_run.py:227
Finding

Automatic Approval of Workspace Trust and Permission-Bypass Warnings

Content
View full analysis
Remediation
View remediation
&2 exit 2 ;; *) echo "Error: unsupported permission mode" >&2 exit 2 ;; esac ``` The exact allowed modes should be selected according to the deployment's threat model. 4. Configure an explicit minimal `--allowedTools` allowlist rather than inheriting broad tool availability. 5. Run Claude Code in a sandbox or container with: - A read-only filesystem except for the selected workspace and result directory. - No access to SSH keys, cloud credentials, or unrelated home-directory files. - Restricted network access. - A dedicated unprivileged account. 6. Canonicalize and validate the workspace beneath an approved repository root before launching the agent. 7. Record permission mode and trust decisions in an audit log, but never treat terminal-text matching as authorization. 8. Update `SKILL.md` to disclose the security model, supported permission modes, workspace approval requirements, and isolation assumptions. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (33)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 95)May include surrounding context.

python
def build_agent_teams_env(args: argparse.Namespace) -> dict[str, str]:
    """Build environment dict with Agent Teams support."""
    env = os.environ.copy()
    if args.agent_teams:
        env["CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS"] = "1"
    return env

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill explicitly instructs the agent to run a local shell script and relies on environment variables, but it does not declare any tool restrictions such as permissions or allowed-tools. That creates a capability mismatch where a consumer may invoke shell and env access implicitly without clear scoping, increasing the risk of unintended command execution in arbitrary repository paths derived from user input.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

When ENABLE_CALLBACK is set, the script writes the task prompt and a Telegram group identifier into task metadata and exports callback-related settings for the launched workflow, but this file provides no user-facing notice or confirmation that prompts may be sent to an external messaging group. Because prompts can contain sensitive project details, secrets, or internal task context, this creates a real data-exposure risk through implicit exfiltration to a third-party channel.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 112)May include surrounding context.

python
script_bin = which("script")
    proc_args = [script_bin, "-q", "-c", cmd_str, "/dev/null"] if script_bin else cmd

    proc = subprocess.Popen(
        proc_args,
        cwd=cwd,
        text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 160)May include surrounding context.

python
def tmux_capture(socket_path: str, target: str, lines: int = 200) -> str:
    out = subprocess.check_output(
        tmux_cmd(socket_path, "capture-pane", "-p", "-J", "-t", target, "-S", f"-{lines}"),
        text=True,
    )

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_output (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 160)May include surrounding context.

python
def tmux_capture(socket_path: str, target: str, lines: int = 200) -> str:
    out = subprocess.check_output(
        tmux_cmd(socket_path, "capture-pane", "-p", "-J", "-t", target, "-S", f"-{lines}"),
        text=True,
    )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 192)May include surrounding context.

python
session = args.tmux_session
    target = f"{session}:0.0"

    subprocess.run(tmux_cmd(socket_path, "kill-session", "-t", session), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.check_call(tmux_cmd(socket_path, "new", "-d", "-s", session, "-n", "shell"))

    cwd = args.cwd or os.getcwd()

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 192)May include surrounding context.

python
session = args.tmux_session
    target = f"{session}:0.0"

    subprocess.run(tmux_cmd(socket_path, "kill-session", "-t", session), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.check_call(tmux_cmd(socket_path, "new", "-d", "-s", session, "-n", "shell"))

    cwd = args.cwd or os.getcwd()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 193)May include surrounding context.

python
target = f"{session}:0.0"

    subprocess.run(tmux_cmd(socket_path, "kill-session", "-t", session), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.check_call(tmux_cmd(socket_path, "new", "-d", "-s", session, "-n", "shell"))

    cwd = args.cwd or os.getcwd()

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 193)May include surrounding context.

python
target = f"{session}:0.0"

    subprocess.run(tmux_cmd(socket_path, "kill-session", "-t", session), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    subprocess.check_call(tmux_cmd(socket_path, "new", "-d", "-s", session, "-n", "shell"))

    cwd = args.cwd or os.getcwd()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 199)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 199)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 200)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 224)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 245)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 200)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 224)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 245)May include surrounding context.

python
# Set Agent Teams env var inside tmux session if enabled
    if args.agent_teams:
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", "export CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"))
        subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))
        time.sleep(0.3)

    claude_parts = [args.claude_bin]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 223)May include surrounding context.

python
claude_parts += args.extra

    launch = f"cd {shlex.quote(cwd)} && " + " ".join(shlex.quote(p) for p in claude_parts)
    subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", launch))
    subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))

    # Workspace trust prompt (first run in a new folder).

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.check_call (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 223)May include surrounding context.

python
claude_parts += args.extra

    launch = f"cd {shlex.quote(cwd)} && " + " ".join(shlex.quote(p) for p in claude_parts)
    subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "-l", "--", launch))
    subprocess.check_call(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"))

    # Workspace trust prompt (first run in a new folder).

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

Interactive mode automatically confirms both the workspace trust prompt and the bypass-permissions warning without requiring explicit user consent. In this skill context, that is especially dangerous because the wrapper is designed to launch interactive agent tasks and can silently suppress safety gates that are meant to protect the local workspace and permission model.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 228)May include surrounding context.

python
# Workspace trust prompt (first run in a new folder).
    if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=20):
        subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
        time.sleep(0.8)
        if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=2):
            subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "1"), check=False)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 232)May include surrounding context.

python
# Workspace trust prompt (first run in a new folder).
    if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=20):
        subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
        time.sleep(0.8)
        if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=2):
            subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "1"), check=False)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 239)May include surrounding context.

python
# Workspace trust prompt (first run in a new folder).
    if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=20):
        subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
        time.sleep(0.8)
        if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=2):
            subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "1"), check=False)

Tainted flow: 'socket_path' from os.environ.get (line 187, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/vendor/claude_code_run.py (reported line 228)May include surrounding context.

python
# Workspace trust prompt (first run in a new folder).
    if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=20):
        subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "Enter"), check=False)
        time.sleep(0.8)
        if tmux_wait_for_text(socket_path, target, "Yes, I trust this folder", timeout_s=2):
            subprocess.run(tmux_cmd(socket_path, "send-keys", "-t", target, "1"), check=False)

Static analysis

No suspicious patterns detected.