Back to skill

Security audit

Tmux Manager

Security checks for vulnerabilities and agentic risk

Overview

This skill is a real tmux manager, but its examples and implementation expose high-impact command execution and agent permission bypasses that users should review carefully before installing.

Install only if you are comfortable with a skill that can start, stop, and restart tmux sessions, run commands from YAML, and send commands into terminal panes. Review any tmux-sessions.yaml before use, avoid copying the sample AI-agent commands that skip approvals, prefer --dry-run and --list before kill or restart, and avoid using --tail on untrusted or oddly named tmux targets until the temp-file and pipe-pane handling is fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tmux-manager.py:458
Finding
Shell Command Injection Through the Tail Target<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tmux-manager.py:458-468` **Vulnerability Type**: OS command injection through an unquoted shell command **Risk Level**: High ### Vulnerable Code ```python session_name = target.split(":")[0] if not session_exists(session_name): sys.exit(f"Error: session '{session_name}' is not running.") log_file = os.path.join(tempfile.gettempdir(), f"tmux-tail-{target.replace(':', '-')}.log") # Start piping pane output to log file subprocess.run( ["tmux", "pipe-pane", "-t", target, f"cat >> {log_file}"], check=True ) ``` ### Technical Analysis The `target` value originates from the user-controlled `-s SESSION[:WINDOW]` argument. Although the code verifies that the session portion exists, it does not validate the complete target or restrict characters in the window portion. The complete target is incorporated into `log_file`, which is then interpolated without shell quoting into the command supplied to `tmux pipe-pane`. The command passed to `pipe-pane` is interpreted by a shell. Consequently, shell metacharacters in a valid tmux window name, such as semicolons, redirection operators, command substitutions, or comment characters, can change the meaning of the command. Using a subprocess argument array only protects the outer invocation of `tmux`; it does not protect the nested command that tmux subsequently passes to a shell. ### Attack Path 1. The attacker creates, renames, or persuades the user to create a window in an existing tmux session with shell metacharacters in its name. 2. The attacker invokes or persuades the user to invoke: ```bash tmux-manager.py --tail -s 'work:x; touch PWNED; #' ``` 3. The session check validates only `work`, which is a legitimate running session. 4. The target-derived filename produces a nested shell command resembling: ```bash cat >> /tmp/tmux-tail-work-x; touch PWNED; #.log ``` 5. `tmux pipe-pane` executes the injected command in the securit ...[truncated 637 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not derive shell commands from an unvalidated tmux target. - Validate session and window identifiers against a strict allowlist before using them, for example a narrowly defined set of alphanumeric characters, underscores, periods, and hyphens. - Shell-quote any path passed to `tmux pipe-pane` with `shlex.quote()`: ```python import shlex pipe_command = f"cat >> {shlex.quote(log_file)}" subprocess.run(["tmux", "pipe-pane", "-t", target, pipe_command], check=True) ``` - Prefer a design that avoids a nested shell entirely, if supported by the tmux integration. - Resolve the supplied target through tmux and reject it if it does not exactly match an existing, expected session/window identifier. - Add regression tests using targets containing spaces, semicolons, command substitutions, redirection operators, quotes, and newline characters. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tmux-manager.py:455
Finding
Predictable Temporary Log File Enables Symlink Attacks and Pane-Output Exposure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tmux-manager.py:455-480` **Vulnerability Type**: Insecure temporary-file creation and symlink following **Risk Level**: High ### Vulnerable Code ```python def tail_pane(target): """Stream live output from a pane via pipe-pane + tail -f.""" import tempfile session_name = target.split(":")[0] if not session_exists(session_name): sys.exit(f"Error: session '{session_name}' is not running.") log_file = os.path.join(tempfile.gettempdir(), f"tmux-tail-{target.replace(':', '-')}.log") # Start piping pane output to log file subprocess.run( ["tmux", "pipe-pane", "-t", target, f"cat >> {log_file}"], check=True ) print(f"Tailing output from '{target}' (Ctrl+C to stop)\n{'-' * 50}") try: subprocess.run(["tail", "-f", log_file]) except KeyboardInterrupt: pass finally: # Stop piping subprocess.run(["tmux", "pipe-pane", "-t", target], check=False) try: os.unlink(log_file) except OSError: pass ``` ### Technical Analysis The tail log path is deterministic and stored in the system-wide temporary directory. It is not created atomically by the Python process, and the code performs no checks for pre-existing files, symbolic links, file ownership, or file type. The nested `cat >>` operation follows symbolic links. A local attacker who can write to the temporary directory can therefore create the expected path as a symbolic link before the victim starts tailing. Pane output will then be appended to the linked destination if the victim has permission to write to it. If a normal file is created under a permissive process umask, other local users may also be able to read pane output that can contain commands, tokens, paths, build logs, or other sensitive development information. The cleanup operation unlinks the pathname without verifying that it still identifies the file or ...[truncated 1419 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the temporary file atomically with `tempfile.mkstemp()` or `NamedTemporaryFile(delete=False)`. - Set permissions to `0600` and retain the returned descriptor: ```python fd, log_file = tempfile.mkstemp(prefix="tmux-tail-", suffix=".log") os.fchmod(fd, 0o600) os.close(fd) ``` - Do not place unvalidated target text directly into a filename. - Before use and cleanup, verify the file is still a regular file owned by the current user and has not been replaced. - Use a private temporary directory created with `tempfile.TemporaryDirectory()` and restrictive permissions. - Ensure cleanup occurs reliably and does not unlink a pathname that has been replaced by another process. - Combine this remediation with shell quoting or elimination of the nested shell command, as described in the command-injection finding. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
README.md:34
Finding
Example Configuration Disables AI-Agent Permission Safeguards by Default<![CDATA[ ## Vulnerability Details **File Location**: `README.md:34-59`; `SKILL.md:163-194` **Vulnerability Type**: Unsafe privileged execution defaults in recommended configuration **Risk Level**: High ### Vulnerable Documentation ```yaml sessions: - name: Project_1 session_group: Project_1 working_dir: ~/Projects/Project_1 windows: - name: claude window_group: claude command: "claude --dangerously-skip-permissions --continue" - name: gemini window_group: gemini command: "gemini -y --resume" - name: shell - name: Project_2 session_group: Project_2 working_dir: ~/Projects/Project_2 env: NODE_ENV: development PORT: 3000 windows: - name: claude window_group: claude command: "claude --dangerously-skip-permissions --continue" - name: gemini window_group: gemini command: "gemini -y --resume" - name: shell window_group: shell ``` Equivalent unsafe commands are repeated in the sample configuration in `SKILL.md`: ```yaml command: "claude --dangerously-skip-permissions --continue" command: "gemini -y --resume" ``` ### Technical Analysis The primary configuration examples recommend launching Claude with `--dangerously-skip-permissions` and Gemini with the automatic-approval option `-y`. These options reduce or bypass interactive authorization safeguards for tool use and command execution. The tmux manager does not need these elevated agent execution modes to perform its legitimate task. Presenting them as normal sample configuration encourages users to grant autonomous agents broader authority over the project directory and user account than is necessary. This is especially dangerous when an agent processes attacker-controlled repository files, issue text, dependency metadata, generated logs, or other content containing prompt-injection instructions. ### Attack Path 1. A user copies the documented sample configu ...[truncated 1104 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove permission-bypass and automatic-approval flags from all default and sample configurations. - Use safe commands as the primary examples: ```yaml command: "claude --continue" command: "gemini --resume" ``` - If bypass modes must be documented, place them in a clearly separated advanced section with explicit warnings. - Recommend running autonomous agents inside a restricted container, virtual machine, or sandbox with: - A dedicated unprivileged account. - Read-only mounts where possible. - Minimal repository scope. - No inherited cloud or production credentials. - Restricted network access. - Explicit command allowlists. - Require interactive approval for destructive commands, credential access, network transmission, package installation, and changes outside the target repository. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/tmux-manager.py:3
Finding
Open-Ended Runtime Dependency Resolution Creates Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tmux-manager.py:3-7` **Vulnerability Type**: Unpinned third-party runtime dependency **Risk Level**: Medium ### Vulnerable Code ```python # /// script # requires-python = ">=3.10" # dependencies = [ # "pyyaml>=6.0", # ] # /// ``` ### Technical Analysis The project directs users to execute the script with `uv run`, while the inline dependency metadata accepts any PyYAML version at or above 6.0. No dependency lockfile or integrity hash is present in the audited project. As a result, executions performed at different times may resolve different package versions. A future compromised, malicious, or unexpectedly incompatible release satisfying the constraint could be installed and imported without review. Because dependency code executes inside the Python process, it receives the same filesystem, environment, and network access as the manager. No evidence was found that the current `pyyaml` package name is typosquatted or that a currently resolved release is malicious. The risk arises from unrestricted future resolution and lack of reproducibility. ### Attack Path 1. A user follows the documented command: ```bash uv run scripts/tmux-manager.py --all ``` 2. `uv` resolves the inline requirement `pyyaml>=6.0`. 3. A package version not previously reviewed by the project may satisfy the requirement and be downloaded. 4. The installed package is imported by: ```python import yaml ``` 5. Any malicious package initialization code would execute with the privileges and environment of the user running the manager. Successful exploitation requires compromise of the dependency distribution channel or publication of a harmful release that satisfies the declared constraint. ### Impact Assessment A compromised dependency could execute arbitrary Python code as the invoking user, read configuration and environment data, modify files, access user credentials, interfere with tmux sessions, or ...[truncated 123 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin PyYAML to a specific audited version rather than using an open-ended lower bound: ```python # dependencies = [ # "pyyaml==<audited-version>", # ] ``` - Commit a dependency lockfile generated by the selected package-management workflow. - Use package hashes or integrity verification where supported. - Establish an explicit dependency-update process that includes vulnerability scanning, release-note review, and testing. - Configure package installation to use a trusted registry and avoid unapproved additional package indexes. - Periodically update the pin after reviewing newer releases rather than leaving resolution unconstrained. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
def run_hook(hook_cmd, label):
    print(f"      [hook:{label}] {hook_cmd}")
    result = subprocess.run(hook_cmd, shell=True)
    if result.returncode != 0:
        print(f"      [!] {label} hook exited with code {result.returncode}")
    return result.returncode
Confidence
100% confidence
Finding
This is a classic tool-parameter abuse issue: untrusted configuration data is passed directly into `subprocess.run(..., shell=True)`. In the skill context, that means a malicious or tampered session config can cause arbitrary shell execution on the host, potentially leading to data theft, persistence, or full system compromise.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README explicitly documents destructive commands such as `--all --kill` and `tmux kill-server` without warning that they will terminate active tmux sessions and may abruptly stop long-running jobs, shells, or editors. In a skill whose purpose is to manage tmux sessions, users are likely to copy-paste these examples directly, so the lack of cautionary language materially increases the chance of accidental operational disruption.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill exposes shell-capable behavior and documents execution of a local Python wrapper around tmux, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization gap: an agent may invoke shell/MCP-capable actions more broadly than intended, including session creation, command injection into tmux panes, and execution of host-shell hooks defined in config.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd, check=True):
    return subprocess.run(cmd, check=check)


def session_exists(name):
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def session_exists(name):
    result = subprocess.run(["tmux", "has-session", "-t", name],
                            capture_output=True, check=False)
    return result.returncode == 0
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
99% confidence
Finding
The skill is explicitly designed to execute arbitrary shell content from configuration hooks and to send arbitrary commands into tmux panes/windows. In an agent-skill context, this materially expands capability from 'manage tmux sessions' to 'execute arbitrary host commands', making prompt/config abuse significantly more dangerous.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_hook(hook_cmd, label):
    print(f"      [hook:{label}] {hook_cmd}")
    result = subprocess.run(hook_cmd, shell=True)
    if result.returncode != 0:
        print(f"      [!] {label} hook exited with code {result.returncode}")
    return result.returncode
Confidence
99% confidence
Finding
`run_hook` executes config-supplied `pre_hook` and `post_hook` values with `shell=True`, allowing arbitrary shell command execution. Because the YAML config is an input to the skill, anyone who can influence that config can run commands on the host, which exceeds normal tmux session management and can lead to full local compromise.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Apply session-level env vars
    for key, val in session_env.items():
        subprocess.run(["tmux", "setenv", "-t", name, key, str(val)], check=True)

    # Handle first window panes or command
    _setup_window(name, first_win_name, first_win, session_dir)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Focus a specific window if requested
    if focus:
        subprocess.run(["tmux", "select-window", "-t", f"{name}:{focus}"], check=False)

    # Post-hook
    if post_hook:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def restart_sessions(sessions, config_path, dry_run=False):
    print(f"Config: {config_path}\n")
    if not dry_run:
        subprocess.run(["tmux", "start-server"], check=True)
    for session_cfg in sessions:
        name = session_cfg.get("name")
        if not name:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def restart_sessions(sessions, config_path, dry_run=False):
    print(f"Config: {config_path}\n")
    if not dry_run:
        subprocess.run(["tmux", "start-server"], check=True)
    for session_cfg in sessions:
        name = session_cfg.get("name")
        if not name:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"  [dry] would kill and recreate: {name}")
        else:
            if session_exists(name):
                subprocess.run(["tmux", "kill-session", "-t", name], check=True)
                print(f"  [-] {name} (killed)")
            create_session(name, session_cfg, dry_run=False)
    print(f"\nDone: {len(sessions)} restarted.")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"  [dry] would kill and recreate: {name}")
        else:
            if session_exists(name):
                subprocess.run(["tmux", "kill-session", "-t", name], check=True)
                print(f"  [-] {name} (killed)")
            create_session(name, session_cfg, dry_run=False)
    print(f"\nDone: {len(sessions)} restarted.")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
log_file = os.path.join(tempfile.gettempdir(), f"tmux-tail-{target.replace(':', '-')}.log")

    # Start piping pane output to log file
    subprocess.run(
        ["tmux", "pipe-pane", "-t", target, f"cat >> {log_file}"],
        check=True
    )
Confidence
87% confidence
Finding
This uses `tmux pipe-pane` with a command string containing a path derived from the `target` value. Since tmux executes the pipe command through a shell, an attacker controlling the session/window target could potentially inject shell metacharacters via the log filename construction, resulting in command execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"Tailing output from '{target}'  (Ctrl+C to stop)\n{'-' * 50}")

    try:
        subprocess.run(["tail", "-f", log_file])
    except KeyboardInterrupt:
        pass
    finally:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
pass
    finally:
        # Stop piping
        subprocess.run(["tmux", "pipe-pane", "-t", target], check=False)
        try:
            os.unlink(log_file)
        except OSError:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This manifest describes actions like creating, killing, restarting sessions, sending commands, and running hooks, which can affect running terminal workloads and session state. The text does not include any user-facing warning about possible disruption, command execution, or impact on active sessions.

Missing User Warnings

Low
Confidence
87% confidence
Finding
This markdown file documents `--kill` and `--restart`, which can terminate tmux sessions and disrupt or discard active interactive work, but it does not explicitly warn users about that impact. Although the commands are named clearly, the skill description lacks a user-facing caution about killing active sessions or processes.

Context-Inappropriate Capability

Low
Confidence
84% confidence
Finding
For a skill framed as tmux session management, the validator and lister inspect arbitrary working_dir paths on the local filesystem and print resolved directories. While local path handling is connected to tmux setup, enumerating and disclosing filesystem existence/status is an additional capability not stated in the manifest description.

Static analysis

No suspicious patterns detected.