Back to skill

Security audit

Clawhub Skill Deploy Pilot

Security checks for vulnerabilities and agentic risk

Overview

This deployment skill is not clearly malicious, but it can make production-impacting changes and has under-scoped safeguards that users should review carefully.

Review this skill before installing, especially for production use. Only use it with trusted stack configuration, avoid script health checks and hooks from untrusted sources, do not rely on the advertised approval or rollback behavior, and run it with the least privileges possible because Docker/SSH deployment access can affect services and host data.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
deploy-pilot.py:312
Finding

Arbitrary Command Execution Through Configurable Health Checks and Deployment Hooks

Content
View full analysis
dict: """Execute custom script for health check.""" script = check.get("script", "") try: result = subprocess.run( script, shell=True, capture_output=True, text=True, timeout=check.get("timeout", 30) ) ok = result.returncode == 0 ``` ```python # Pre-deploy hooks for hook in stack.get("hooks", {}).get("pre", []): print(f"{Colors.status_wait()} Running pre-hook: {hook}") try: subprocess.run(hook, shell=True, check=True) print(f"{Colors.status_ok()} Pre-hook completed") except subprocess.CalledProcessError as e: print(f"{Colors.status_fail()} Pre-hook failed: {e}") return False ``` ```python # Post-deploy hooks if success: for hook in stack.get("hooks", {}).get("post", []): print(f"{Colors.status_wait()} Running post-hook: {hook}") try: subprocess.run(hook, shell=True, check=True) print(f"{Colors.status_ok()} Post-hook completed") except subprocess.CalledProcessError as e: print(f"{Colors.status_fail()} Post-hook failed: {e}") ``` ### Technical Analysis Health-check scripts and pre/post-deployment hooks are obtained from mutable stack configuration and passed directly to `subprocess.run()` with `shell=True`. The shell interprets command separators, substitutions, pipelines, redirections, environment expansion, and other shell syntax. No command allowlist, executable-path validation, argument separation, file ownership verification, or trust-boundary check is applied before execution. Consequently, a malicious configuration value is treated as exe ...[truncated 1512 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
deploy-pilot.py:172
Finding

Snapshot Directory Escape Through Unvalidated Stack Names

Content
View full analysis
bool: """Export current state as snapshot.""" snapshot_dir = WORKSPACE / "snapshots" / snapshot_name snapshot_dir.mkdir(parents=True, exist_ok=True) # Copy compose file shutil.copy(self.compose_file, snapshot_dir / self.compose_file.name) # Save container state containers = self.ps() with open(snapshot_dir / "state.json", 'w') as f: json.dump(containers, f, indent=2) ``` The attacker-controlled stack name becomes the project and snapshot name without validation: ```python def add_stack(self, args): """Add a new managed stack.""" name = args.name stack_type = args.type if name in self.state.list_stacks(): print(f"{Colors.status_fail()} Stack '{name}' already exists") return False if stack_type == "docker": config = {"compose_file": args.path, "project_name": name} ``` ```python snapshot_name = f"{project_name}-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}" executor.create_snapshot(snapshot_name) ``` ### Technical Analysis The command-line stack name is stored as `project_name` and later incorporated directly into `snapshot_name`. That name is joined to the intended snapshot root with `pathlib` without validation or containment verification. If `snapshot_name` is absolute, `pathlib` discards the preceding `WORKSPACE / "snapshots"` components. Names containing traversal components can likewise resolve outside the intended snapshot tree. The code then creates the selected directory, copies the Compose file into it, and writes `state.json`. The destination filename for the copied Compose file is fixed to the source basename, so this is not a fully a ...[truncated 1474 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
deploy-pilot.py:233
Finding

Server-Side Request Forgery Through Unrestricted HTTP Health-Check Endpoints

Content
View full analysis
dict: """HTTP endpoint health check.""" url = check.get("endpoint", "") expected_code = check.get("expected_code", 200) timeout = check.get("timeout", 30) try: import urllib.request req = urllib.request.Request(url, method="GET") start = time.time() response = urllib.request.urlopen(req, timeout=timeout) ``` ### Technical Analysis The HTTP health checker makes a request to the configured endpoint without validating the URL scheme, hostname, destination address, or redirect target. It does not reject loopback, private, link-local, multicast, or otherwise sensitive network ranges. Because the request originates from the deployment host, an attacker who can control health-check configuration can use the process's network position to contact services that may not be reachable from the attacker's own system. Automatic redirect handling can also allow an initially acceptable endpoint to redirect to a prohibited internal destination. The implementation does not read or return the response body, which limits direct data extraction. However, status behavior, success or failure, exceptions, and response timing still provide an internal network probing capability. Requests can also trigger state changes on internal services that improperly expose state-changing GET endpoints. ### Attack Path 1. An attacker gains the ability to define or modify a stack's HTTP health-check endpoint in deployment state. 2. The endpoint is set to an internal, loopback, private-network, or link-local service. 3. A user or automated job invokes a health check or deploys the affected stack. 4. `urllib.request.urlopen()` sends the request from the deployment host. 5. The attacker observes reported status, e ...[truncated 882 chars]
Remediation
View remediation
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (23)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

Passing untrusted script content directly to subprocess.run with shell=True is a classic arbitrary command execution vulnerability. An attacker controlling health-check configuration can run any shell command available to the deployment process, leading to host compromise, secret theft, or destructive actions.

Content

Scanner excerpt · deploy-pilot.py (reported line 318)May include surrounding context.

python
script = check.get("script", "")
        
        try:
            result = subprocess.run(
                script,
                shell=True,
                capture_output=True,

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

Executing hook strings with shell=True lets stack configuration control arbitrary shell execution on the host. In a deployment automation tool, that can be chained into container breakout assistance, credential exfiltration, filesystem tampering, or full CI/CD compromise.

Content

Scanner excerpt · deploy-pilot.py (reported line 404)May include surrounding context.

python
for hook in stack.get("hooks", {}).get("pre", []):
            print(f"{Colors.status_wait()} Running pre-hook: {hook}")
            try:
                subprocess.run(hook, shell=True, check=True)
                print(f"{Colors.status_ok()} Pre-hook completed")
            except subprocess.CalledProcessError as e:
                print(f"{Colors.status_fail()} Pre-hook failed: {e}")

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
100% confidence
Finding

This post-hook execution repeats the same shell=True arbitrary-command pattern, enabling untrusted configuration to run code after deployment completes. That timing can be especially dangerous because new secrets, endpoints, or service tokens may now be available for theft or misuse.

Content

Scanner excerpt · deploy-pilot.py (reported line 423)May include surrounding context.

python
for hook in stack.get("hooks", {}).get("post", []):
                print(f"{Colors.status_wait()} Running post-hook: {hook}")
                try:
                    subprocess.run(hook, shell=True, check=True)
                    print(f"{Colors.status_ok()} Post-hook completed")
                except subprocess.CalledProcessError as e:
                    print(f"{Colors.status_fail()} Post-hook failed: {e}")

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The rollback command claims success without actually restoring a prior version, creating a dangerous false sense of recovery during incidents. In production deployment tooling, failed rollback semantics can prolong outages, block containment, and cause operators to make unsafe assumptions in emergencies.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 35)May include surrounding context.

bash
# Clone/download the skill
mkdir -p ~/.openclaw/workspace/skills/deploy-pilot
cd ~/.openclaw/workspace/skills/deploy-pilot

# Copy SKILL.md and scripts

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
92% confidence
Finding

The documented commands allow both skipping health checks and skipping approval for deployments, removing two key safeguards before modifying running services. If used by an agent or copied into automation, these options can cause unsafe or unreviewed production changes and increase the chance of outages or deployment of malicious/unverified versions.

Content

Scanner excerpt · SKILL.md (reported line 141)May include surrounding context.

Skip health check

deploy-pilot deploy --skip-health-check

Force deployment (skip approval)

deploy-pilot deploy --force

text

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The documentation explicitly supports scheduled deployments and hooks that can run deployment actions automatically, which can modify live infrastructure and potentially affect data. While this is expected for a deployment tool, the nearby guidance does not clearly emphasize the operational risk, required safeguards, or that hooks/rollbacks may execute arbitrary system-changing scripts against production systems.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
84% confidence
Finding

The example normalizes deployment to an environment with '--force' and explicitly notes 'no approval needed', which weakens human oversight for an operation that changes running infrastructure. In an agent skill context, examples often become copied commands, so this can encourage autonomous execution of impactful changes without review.

Content

Scanner excerpt · SKILL.md (reported line 375)May include surrounding context.

md
deploy-pilot add docker dev  /compose/docker-compose.yml --env=dev
deploy-pilot add docker prod /compose/docker-compose.yml --env=prod

# Deploy to dev (no approval needed)
deploy-pilot deploy dev --force

# Deploy to prod (requires approval)

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
86% confidence
Finding

The 'force' completion path bypasses the approval workflow after a deployment request is already pending, undermining the control the product presents as a safety mechanism. In practice this can let an operator or automation convert a gated deployment into an ungated one, defeating separation-of-duties and increasing the risk of unauthorized changes.

Content

Scanner excerpt · SKILL.md (reported line 450)May include surrounding context.

Check approval status

deploy-pilot status

Force completion (skip approval, use with caution)

deploy-pilot force

text

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The docstring advertises approval workflows, but the implementation does not enforce any approval gate and even exposes a force flag to skip it. This is a security design integrity issue because operators may rely on non-existent change control while the tool can perform production-impacting actions immediately.

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 · deploy-pilot.py (reported line 121)May include surrounding context.

python
"""Pull latest images."""
        cmd = ["docker", "compose", "-f", str(self.compose_file), "pull"]
        try:
            subprocess.run(cmd, cwd=str(self.work_dir), check=True)
            return True
        except subprocess.CalledProcessError:
            return 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 · deploy-pilot.py (reported line 130)May include surrounding context.

python
"""Pull latest images."""
        cmd = ["docker", "compose", "-f", str(self.compose_file), "pull"]
        try:
            subprocess.run(cmd, cwd=str(self.work_dir), check=True)
            return True
        except subprocess.CalledProcessError:
            return 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 · deploy-pilot.py (reported line 139)May include surrounding context.

python
"""Pull latest images."""
        cmd = ["docker", "compose", "-f", str(self.compose_file), "pull"]
        try:
            subprocess.run(cmd, cwd=str(self.work_dir), check=True)
            return True
        except subprocess.CalledProcessError:
            return False

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

The skill can stop containers via docker compose down during rollback-on-health-failure, which can interrupt services and alter running state. While the overall tool is for deployment automation, there is no explicit user-facing warning that failed health checks may automatically bring containers down.

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 · deploy-pilot.py (reported line 148)May include surrounding context.

python
"""List containers."""
        cmd = ["docker", "compose", "-f", str(self.compose_file), "ps", "--format", "json"]
        try:
            result = subprocess.run(cmd, cwd=str(self.work_dir), capture_output=True, text=True, check=True)
            return json.loads(result.stdout) if result.stdout else []
        except (subprocess.CalledProcessError, json.JSONDecodeError):
            return []

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
84% confidence
Finding

The SSH health check executes a remote command taken from configuration, which can run arbitrary commands on the target host. In a deployment skill, configuration may be modified or supplied by other components, so this expands the tool into remote code execution capability without strong guardrails or restriction.

Content

Scanner excerpt · deploy-pilot.py (reported line 293)May include surrounding context.

python
command = check.get("command", "echo ok")
        
        try:
            result = subprocess.run(
                ["ssh", target, command],
                capture_output=True,
                text=True,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill exposes custom script health checks that run arbitrary shell commands without adequate disclosure or safety boundaries. In this context, users may view health checks as passive validation, but they actually provide full local code execution, making the behavior materially more dangerous.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

This health check executes attacker-controlled script text with shell=True, creating a direct command-injection and arbitrary code execution path on the local system. In deployment automation, this is especially dangerous because the process may run with elevated privileges and access to production credentials, Docker, and host resources.

Content

Scanner excerpt · deploy-pilot.py (reported line 318)May include surrounding context.

python
script = check.get("script", "")
        
        try:
            result = subprocess.run(
                script,
                shell=True,
                capture_output=True,

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Pre/post deploy hooks are not clearly disclosed as shell-executed commands, which can mislead users into enabling untrusted configuration that results in arbitrary code execution. Because this is a deployment skill, hooks execute in a high-value environment with broad operational access.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

Pre-deploy hooks are executed with shell=True from stack configuration, allowing arbitrary local command execution before deployment. Because deployment tools often run with access to Docker, files, secrets, and CI/CD environments, a malicious or tampered hook can fully compromise the host or pipeline.

Content

Scanner excerpt · deploy-pilot.py (reported line 404)May include surrounding context.

python
for hook in stack.get("hooks", {}).get("pre", []):
            print(f"{Colors.status_wait()} Running pre-hook: {hook}")
            try:
                subprocess.run(hook, shell=True, check=True)
                print(f"{Colors.status_ok()} Pre-hook completed")
            except subprocess.CalledProcessError as e:
                print(f"{Colors.status_fail()} Pre-hook failed: {e}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

Post-deploy hooks have the same arbitrary shell execution issue as pre-hooks, but after deployment they may also access freshly deployed services and secrets. This makes them a powerful persistence or exfiltration mechanism if configuration is untrusted or compromised.

Content

Scanner excerpt · deploy-pilot.py (reported line 423)May include surrounding context.

python
for hook in stack.get("hooks", {}).get("post", []):
                print(f"{Colors.status_wait()} Running post-hook: {hook}")
                try:
                    subprocess.run(hook, shell=True, check=True)
                    print(f"{Colors.status_ok()} Post-hook completed")
                except subprocess.CalledProcessError as e:
                    print(f"{Colors.status_fail()} Post-hook failed: {e}")

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
88% confidence
Finding

The presence of a flag to skip approval workflow is risky because the tool already lacks actual approval enforcement, so this suggests bypassable or illusory governance around production changes. In an agent-driven deployment context, such flags can enable unauthorized autonomous changes with real service impact.

Content

Scanner excerpt · deploy-pilot.py (reported line 573)May include surrounding context.

python
deploy_parser.add_argument("stack", help="Stack name")
    deploy_parser.add_argument("--version", help="Version/tag to deploy")
    deploy_parser.add_argument("--skip-health-check", action="store_true", help="Skip health checks")
    deploy_parser.add_argument("--force", action="store_true", help="Skip approval workflow")
    
    # rollback
    rollback_parser = subparsers.add_parser("rollback", help="Rollback to previous version")

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · skill.json (reported line 48)May include surrounding context.

json
"exec:pvesh",
    "exec:ssh",
    "file:read ~/.openclaw/workspace/deploy-pilot",
    "file:write ~/.openclaw/workspace/deploy-pilot",
    "net:http"
  ],
  "features": {

Static analysis

No suspicious patterns detected.