Back to skill

Security audit

Portainer Docker Control

Security checks for vulnerabilities and agentic risk

Overview

This Portainer skill is coherent, but it needs review because it can control live Docker infrastructure while handling API credentials insecurely.

Install only if you trust the publisher and are comfortable giving this skill control over the Portainer environments reachable by its API token. Use a least-privilege Portainer token, avoid production use without human confirmation, secure any local credential file with restrictive permissions, and remove curl -k or require a trusted certificate before use.

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
portainer.sh:29
Finding

TLS Certificate Verification Disabled for Authenticated Portainer Requests

Content
View full analysis

Vulnerability Details

File Location: portainer.sh, lines 29–39
Vulnerability Type: Improper certificate validation
Risk Level: High

Vulnerable Code

bash
api_get() {
    curl -s -k -H "$AUTH_HEADER" "$API$1"
}

api_post() {
    curl -s -k -X POST -H "$AUTH_HEADER" -H "Content-Type: application/json" "$API$1" -d "$2"
}

api_put() {
    curl -s -k -X PUT -H "$AUTH_HEADER" -H "Content-Type: application/json" "$API$1" -d "$2"
}

Technical Analysis

The -k option instructs curl to accept an HTTPS server certificate without validating its authenticity. Every request made through these functions includes the Portainer API key in the X-API-Key header.

Disabling certificate validation eliminates the server-authentication protection provided by TLS. An attacker who can intercept or redirect network traffic can present an arbitrary certificate, receive the API key, inspect requests, and supply forged API responses. The affected functions include both read operations and privileged operations such as starting or stopping containers and redeploying stacks.

Although contacting the configured Portainer server is necessary for the declared functionality, disabling certificate validation is not necessary and exposes credentials beyond the minimum safe privilege boundary.

Attack Path

  1. A user configures an HTTPS Portainer URL and invokes the script.
  2. An attacker gains a network interception position or manipulates DNS or routing.
  3. The attacker presents a self-signed or otherwise invalid certificate.
  4. Because curl -k disables validation, the script accepts the certificate.
  5. The script sends the X-API-Key header to the attacker-controlled endpoint.
  6. The attacker reuses the stolen key against the genuine Portainer server.
  7. The attacker performs any container, stack, endpoint, or log operation authorized by that API token.

Impact Assessment

Successful exploi ...[truncated 445 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove the -k option from every curl invocation.

  • Require Portainer to use a certificate trusted by the operating system.

  • For deployments using a private certificate authority, support an explicit CA bundle, for example:

    bash
    curl --fail-with-body --silent --show-error \
      --cacert "$PORTAINER_CA_FILE" \
      -H "$AUTH_HEADER" \
      "$API$1"
    
  • Do not silently fall back to insecure certificate handling when CA validation fails.

  • Consider certificate or public-key pinning where the deployment model supports secure pin rotation.

  • Add --fail-with-body and --show-error so HTTP and transport failures are not mistaken for successful operations.

  • Document the certificate requirements and provide instructions for installing a private CA securely.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:252
Finding

Plaintext API Token Storage Without Restrictive Permission Guidance

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 252–257
Vulnerability Type: Insecure credential storage guidance
Risk Level: Medium

Vulnerable Code

bash
**Solution:**
```bash
# Add to ~/.clawdbot/.env
echo "PORTAINER_URL=https://your-server:9443" >> ~/.clawdbot/.env
echo "PORTAINER_API_KEY=ptr_your_token" >> ~/.clawdbot/.env
text

The same plaintext file is also recommended in the initial setup instructions at `SKILL.md`, lines 54–58.

### Technical Analysis

The documentation directs users to append a Portainer API token to `~/.clawdbot/.env`, but it neither creates the directory and file with restrictive permissions nor verifies their ownership. Shell redirection preserves the permissions of an existing file and creates a new file according to the current process umask. A permissive umask or pre-existing insecure file may therefore leave the token readable by other local users.

Reading a Portainer credential is necessary for the Skill's declared functionality, and the script does not search unrelated secret locations. The risk arises from recommending persistent plaintext storage without applying the minimum controls required for a sensitive credential.

### Attack Path

1. A user follows the documented commands and appends the API token to `~/.clawdbot/.env`.
2. The file is created under a permissive umask, or an existing file has group-readable or world-readable permissions.
3. Another local account, process, or compromised application reads the file.
4. The attacker obtains both `PORTAINER_URL` and `PORTAINER_API_KEY`.
5. The attacker submits authenticated requests directly to the Portainer API.
6. The attacker performs operations allowed by the token.

### Impact Assessment

The maximum impact is determined by the Portainer token's assigned permissions. A broadly privileged token may permit infrastructure enumeration, access to logs and stack metadata, container lifecycle 
...[truncated 209 chars]
Remediation
View remediation

Remediation Suggestions

  • Prefer receiving the credential through a dedicated secret manager or a securely inherited environment variable rather than persisting it in plaintext.

  • If a file must be used, create the directory and file securely before writing:

    bash
    install -d -m 700 "$HOME/.clawdbot"
    install -m 600 /dev/null "$HOME/.clawdbot/.env"
    
  • Verify that the file is owned by the current user and reject symlinks or files with unsafe permissions.

  • Set a restrictive umask, such as umask 077, before creating credential files.

  • Update every setup and troubleshooting example to include these controls.

  • Recommend a least-privilege Portainer token limited to the endpoints and operations the user actually needs.

  • Document token rotation and revocation procedures for suspected disclosure.

T09 · Insecure Skill Coding Practices

Warning
Location
portainer.sh:12
Finding

Unsafe Environment-File Parsing Can Override Trusted Configuration

Content
View full analysis

Vulnerability Details

File Location: portainer.sh, lines 12–16
Vulnerability Type: Unsafe configuration parsing and credential redirection
Risk Level: Medium

Vulnerable Code

bash
if [[ -z "$PORTAINER_URL" || -z "$PORTAINER_API_KEY" ]]; then
    ENV_FILE="$HOME/.clawdbot/.env"
    if [[ -f "$ENV_FILE" ]]; then
        export $(grep -E "^PORTAINER_" "$ENV_FILE" | xargs)
    fi
fi

Technical Analysis

The script passes text extracted from the configuration file through xargs and unquoted command substitution. The resulting values are subject to shell word splitting and pathname expansion, so spaces, quotes, wildcard characters, and malformed lines are not handled as literal configuration values.

The condition is also entered when either required variable is missing, but all matching PORTAINER_* entries are exported. Consequently, a value already supplied through the process environment can be overwritten by the file. For example, an environment-provided API key can be combined with a file-controlled PORTAINER_URL, causing the credential to be sent to an unintended host.

The code accepts every variable beginning with PORTAINER_ rather than parsing only the two required keys. It also does not validate file ownership, permissions, symlink status, URL scheme, or destination host. This increases the consequences of a locally modified or incorrectly provisioned configuration file.

Attack Path

  1. An attacker or compromised local process obtains write access to ~/.clawdbot/.env, or causes an unsafe file to be provisioned at that path.
  2. The attacker inserts a PORTAINER_URL value pointing to an attacker-controlled HTTPS server.
  3. The user supplies PORTAINER_API_KEY through the process environment but leaves PORTAINER_URL unset.
  4. Because one required value is missing, the script parses and exports all matching entries from the file.
  5. The file-controlled URL becomes the ...[truncated 956 chars]
Remediation
View remediation

Remediation Suggestions

  • Eliminate export $(... | xargs) and parse only exact, approved keys.

  • Preserve non-empty environment values rather than overwriting them.

  • Read the file line by line without eval, command substitution, or unquoted expansion.

  • Reject duplicate keys, malformed lines, unexpected keys, control characters, and multiline values.

  • Verify that the file is a regular file owned by the current user, is not a symbolic link, and has permissions no broader than 0600.

  • Validate PORTAINER_URL as an HTTPS URL and, where feasible, enforce an explicit allowlist of expected hosts.

  • A safer approach is to use a dedicated configuration parser. For simple values, the script can extract each exact key separately while preserving existing environment values:

    bash
    if [[ -z "$PORTAINER_URL" ]]; then
        PORTAINER_URL=$(sed -n 's/^PORTAINER_URL=//p' "$ENV_FILE" | tail -n 1)
        export PORTAINER_URL
    fi
    
    if [[ -z "$PORTAINER_API_KEY" ]]; then
        PORTAINER_API_KEY=$(sed -n 's/^PORTAINER_API_KEY=//p' "$ENV_FILE" | tail -n 1)
        export PORTAINER_API_KEY
    fi
    
  • Supplement this example with strict validation and rejection of duplicate entries rather than silently selecting one.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (20)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
|------|---------|
| **Portainer** | Version 2.x with API access |
| **Tools** | `curl`, `jq` |
| **Auth** | API Access Token |

### Setup

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
1. **Get API Token from Portainer:**
   - Log into Portainer web UI
   - Click username → My Account
   - Scroll to "Access tokens" → Add access token
   - Copy the token (you won't see it again!)

2. **Configure credentials:**

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
1. **Get API Token from Portainer:**
   - Log into Portainer web UI
   - Click username → My Account
   - Scroll to "Access tokens" → Add access token
   - Copy the token (you won't see it again!)

2. **Configure credentials:**

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · portainer.sh (reported line 183)May include surrounding context.

sh
1. **Get API Token from Portainer:**
   - Log into Portainer web UI
   - Click username → My Account
   - Scroll to "Access tokens" → Add access token
   - Copy the token (you won't see it again!)

2. **Configure credentials:**

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

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

md
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · portainer.sh (reported line 7)May include surrounding context.

sh
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · portainer.sh (reported line 11)May include surrounding context.

sh
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · portainer.sh (reported line 21)May include surrounding context.

sh
set -e

# Load config from environment or .env
PORTAINER_URL="${PORTAINER_URL:-}"
PORTAINER_API_KEY="${PORTAINER_API_KEY:-}"

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The command export $(grep -E "^PORTAINER_" "$ENV_FILE" | xargs) unsafely parses the .env file using shell word splitting. A crafted .env line can inject additional shell tokens or malformed assignments, causing unintended environment manipulation and making the credential-loading path more dangerous than necessary.

Content

Scanner excerpt · portainer.sh (reported line 13)May include surrounding context.

sh
# Try to load from clawdbot .env if not set
if [[ -z "$PORTAINER_URL" || -z "$PORTAINER_API_KEY" ]]; then
    ENV_FILE="$HOME/.clawdbot/.env"
    if [[ -f "$ENV_FILE" ]]; then
        export $(grep -E "^PORTAINER_" "$ENV_FILE" | xargs)
    fi

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
86% confidence
Finding

The API helpers concatenate request paths directly into curl targets and execute privileged Portainer operations without any guardrails, confirmation, or allowlisting. In an agent-skill context, this makes parameter-driven abuse more dangerous because a prompt or calling workflow can trigger disruptive actions such as stopping containers, restarting services, reading logs, or redeploying stacks across endpoints.

Content

Scanner excerpt · portainer.sh (reported line 30)May include surrounding context.

sh
# Helper function for API calls
api_get() {
    curl -s -k -H "$AUTH_HEADER" "$API$1"
}

api_post() {

Credential Access

High
Category
Privilege Escalation
Confidence
71% confidence
Finding

The redeploy logic pulls .Env from the stack definition and re-sends it in the redeploy payload. Stack environment arrays often contain secrets, so this unnecessarily increases secret exposure in process memory, shell variables, and potentially debug or crash surfaces; combined with disabled TLS elsewhere, interception risk increases further.

Content

Scanner excerpt · portainer.sh (reported line 79)May include surrounding context.

sh
# Get stack info for env vars and endpoint
        STACK_INFO=$(api_get "/stacks/$STACK_ID")
        ENDPOINT_ID=$(echo "$STACK_INFO" | jq -r '.EndpointId')
        ENV_VARS=$(echo "$STACK_INFO" | jq -c '.Env')
        GIT_CRED_ID=$(echo "$STACK_INFO" | jq -r '.GitConfig.Authentication.GitCredentialID // 0')
        
        PAYLOAD=$(jq -n \

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding

The skill clearly enables shell-based operational actions against infrastructure, but it does not declare an explicit tool scope or permission boundary. In an agent setting, missing scope metadata can allow broader-than-intended command execution or make dangerous actions appear routine, increasing the chance of unauthorized container control or stack redeploys.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill documents destructive and service-impacting operations like redeploy, stop, and restart without any warning about downtime, production impact, or approval requirements. In an autonomous or semi-autonomous agent context, this normalizes high-impact actions and increases the likelihood of accidental disruption to live services.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script automatically reads PORTAINER_API_KEY and related settings from ~/.clawdbot/.env, which is a sensitive credential source. Although the code comments describe the behavior for developers, there is no user-facing prompt, warning, or runtime disclosure that credentials will be accessed from a local file.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
98% confidence
Finding

All API helper functions use curl with -k, which disables TLS certificate verification for every Portainer request. That allows a man-in-the-middle attacker to intercept or modify responses and capture the X-API-Key, which is especially dangerous because this tool can start/stop containers, read logs, and redeploy stacks.

Content

Scanner excerpt · portainer.sh (reported line 30)May include surrounding context.

sh
# Helper function for API calls
api_get() {
    curl -s -k -H "$AUTH_HEADER" "$API$1"
}

api_post() {

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The logs command makes an HTTP request to retrieve container logs, which may contain sensitive application or user data, but the script provides no explicit warning about that network access or possible exposure. The command usage mentions logs functionality, but it does not disclose the privacy impact of transmitting and displaying potentially sensitive log contents.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The manifest describes a skill for controlling containers and stacks via the Portainer API, but this code also implements local credential discovery by reading ~/.clawdbot/.env. Accessing local user configuration is not explicitly part of the stated Portainer-control purpose, even though it is used to obtain API settings.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.