Back to skill

Security audit

Runpod Media

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its media-generation purpose, but it needs Review because it reads unrelated Telegram credentials and can upload arbitrary local files to external services.

Install only if you are comfortable with the skill sending prompts and media to RunPod, temporarily uploading local inputs to Cloudflare R2, and potentially delivering outputs through Telegram. Review or remove the Telegram curl fallback before use, restrict which local files may be uploaded, and avoid arbitrary endpoint IDs unless you trust the endpoint and understand where the data will go.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:189
Finding

Direct Access to Telegram Bot Credentials Bypasses the Protected Messaging Abstraction

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_utils.py:59
Finding

Arbitrary Readable Local Files Can Be Uploaded to Cloudflare R2

Content
View full analysis
str: """Upload a local file to Cloudflare R2 and return a presigned GET URL (default 1 min).""" import boto3 from botocore.config import Config secrets_path = pathlib.Path.home() / ".openclaw" / "secrets.json" data = json.loads(secrets_path.read_text()) r2 = data.get("cloudflare", {}).get("r2", {}) access_key = r2.get("accessKeyId") secret_key = r2.get("secretAccessKey") endpoint = r2.get("endpoint") bucket = r2.get("bucket", "openclaw") if not all([access_key, secret_key, endpoint]): raise SystemExit("R2 credentials not found in secrets.json under cloudflare.r2") client = boto3.client( "s3", endpoint_url=endpoint, aws_access_key_id=access_key, aws_secret_access_key=secret_key, region_name="auto", config=Config(signature_version="s3v4"), ) file_path = pathlib.Path(path) key = f"uploads/{int(time.time())}_{file_path.name}" content_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream" with open(file_path, "rb") as f: client.put_object(Bucket=bucket, Key=key, Body=f, ContentType=content_type) url = client.generate_presigned_url( "get_object", Params={"Bucket": bucket, "Key": key}, ExpiresIn=expiry, ) print(f" uploaded to R2: {url[:80]}…") return url ``` ```python def ensure_url(path_or_url: str, imgbb_key: str | None = None) -> str: """Return a public URL, uploading to R2 (presigned, 1 min) if a local path is given.""" if path_or_url.startswith("http://") or path_or_url.startswith("https://"): return path_or_url return upload_to_r2(path_or_url, expiry=6 ...[truncated 2166 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/_utils.py:158
Finding

Untrusted Endpoint Response URLs Are Downloaded Without Network or Resource Restrictions

Content
View full analysis
str | None: return ( output.get("video_url") or output.get("image_url") or output.get("result") or output.get("url") or output.get("image") ) def output_dir() -> pathlib.Path: # Save inside OpenClaw workspace so sandboxed agents can read the files d = pathlib.Path.home() / ".openclaw" / "workspace" / "runpod-media" d.mkdir(parents=True, exist_ok=True) return d def save_media(url: str, filename: str) -> pathlib.Path: dest = output_dir() / filename resp = requests.get(url, timeout=120) resp.raise_for_status() dest.write_bytes(resp.content) return dest ``` The generic caller accepts any endpoint ID and passes its output to this function: ```python if output_key and isinstance(output, dict): url = output.get(output_key) if not url: url = get_media_url(output) if url: dest = save_media(url, f"{args.endpoint.replace('-','_')}_{ts}.{ext}") ``` ### Technical Analysis A URL returned by a selected RunPod endpoint is trusted and fetched by the local Skill process. The implementation does not validate: - The URL scheme - The destination hostname - Whether the resolved address is private, loopback, link-local, or otherwise internal - Redirect destinations - The returned content type or media signature - The response `Content-Length` - The total number of bytes downloaded `requests.get()` follows redirects by default. It also buffers the complete response in `resp.content` before writing it, allowing a large response to exhaust process memory and consume disk space. Because `call_endpoint.py` intentionally supports arbitrary RunPod endpoint IDs, the returned media location cannot safely be assumed to belong to a ...[truncated 1238 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/run.sh:17
Finding

Runtime Dependencies Are Unpinned and Resolved During Skill Execution

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (48)

Tainted flow: 'api_key' from os.getenv (line 34, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/_utils.py (reported line 108)May include surrounding context.

python
def runsync(endpoint_id: str, payload: dict, api_key: str, timeout: int = 120) -> dict:
    url = f"https://api.runpod.ai/v2/{endpoint_id}/runsync"
    resp = requests.post(url, json={"input": payload}, headers=_headers(api_key), timeout=timeout)
    resp.raise_for_status()
    data = resp.json()
    if data.get("status") == "FAILED":

Tainted flow: 'api_key' from os.getenv (line 34, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/_utils.py (reported line 118)May include surrounding context.

python
def runsync(endpoint_id: str, payload: dict, api_key: str, timeout: int = 120) -> dict:
    url = f"https://api.runpod.ai/v2/{endpoint_id}/runsync"
    resp = requests.post(url, json={"input": payload}, headers=_headers(api_key), timeout=timeout)
    resp.raise_for_status()
    data = resp.json()
    if data.get("status") == "FAILED":

Tainted flow: 'api_key' from os.getenv (line 34, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/_utils.py (reported line 128)May include surrounding context.

python
url = f"https://api.runpod.ai/v2/{endpoint_id}/status/{job_id}"
    deadline = time.time() + max_wait
    while time.time() < deadline:
        resp = requests.get(url, headers=_headers(api_key), timeout=30)
        resp.raise_for_status()
        data = resp.json()
        status = data.get("status")

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill also supports endpoint discovery, probing, health verification, and local registry modification, none of which are disclosed in the core description. These capabilities materially expand the attack surface by enabling dynamic expansion of what the skill can contact and persist for future use.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill also supports endpoint discovery, probing, health verification, and local registry modification, none of which are disclosed in the core description. These capabilities materially expand the attack surface by enabling dynamic expansion of what the skill can contact and persist for future use.

Content

No source excerpt is available for this finding.

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 12)May include surrounding context.

md
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 18)May include surrounding context.

md
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 25)May include surrounding context.

md
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 144)May include surrounding context.

md
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 14)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 16)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 17)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 33)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 41)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

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 · scripts/_utils.py (reported line 75)May include surrounding context.

python
## API Keys

One key required — add to `~/.openclaw/secrets.json`:

| Key path | Purpose | Get it from |
|----------|---------|-------------|

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill directs the agent to read Telegram bot credentials from secrets and exfiltrate generated files via direct API calls unrelated to the core RunPod function. This bypasses normal tool-mediated delivery controls, introduces another third-party recipient, and expands the skill into credential use plus outbound messaging.

Content

No source excerpt is available for this finding.

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

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

md
### The Problem
The `message` tool with a local `media` path may fail in sandboxed agent modes due to SecretRef resolution not being available for media sends. This is a known OpenClaw limitation.

### The Solution: Use curl + Telegram Bot API directly

Read the bot token from secrets and send via curl — this always works regardless of sandbox mode:

Credential Access

High
Category
Privilege Escalation
Confidence
98% confidence
Finding

This instruction explicitly tells the agent to read a Telegram bot token from ~/.openclaw/secrets.json and use it in direct API requests. Accessing unrelated messaging credentials from within a media-generation skill breaks separation of duties and enables unauthorized outbound communications if abused.

Content

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

Read the bot token from secrets and send via curl — this always works regardless of sandbox mode:

bash
TOKEN=$(cat ~/.openclaw/secrets.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('telegram',{}).get('botToken',''))")

# Send photo
curl -s \

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

This function is specifically designed to load Cloudflare R2 configuration from the shared secrets store, which is outside the skill's stated RunPod-only need. In context, accessing unrelated credentials increases risk because it grants the skill additional capabilities to move data externally.

Content

Scanner excerpt · scripts/_utils.py (reported line 50)May include surrounding context.

python
def _r2_config() -> dict:
    """Load R2 config from secrets.json."""
    cfg = _from_secrets_json("/cloudflare/r2/accessKeyId")
    if not cfg:
        # Return full dict

Credential Access

High
Category
Privilege Escalation
Confidence
89% confidence
Finding

Reading the broader secrets.json structure to return Cloudflare R2 configuration expands access beyond the minimum needed for RunPod operations. Even if not immediately exfiltrated on this line, it unnecessarily exposes additional credentials to the skill runtime.

Content

Scanner excerpt · scripts/_utils.py (reported line 54)May include surrounding context.

python
cfg = _from_secrets_json("/cloudflare/r2/accessKeyId")
    if not cfg:
        # Return full dict
        secrets_path = pathlib.Path.home() / ".openclaw" / "secrets.json"
        data = json.loads(secrets_path.read_text())
        return data.get("cloudflare", {}).get("r2", {})
    return {}

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

This code reads the secrets file to retrieve Cloudflare R2 credentials for actual use in external storage operations. In context, that is a meaningful security concern because the skill now has access to a second set of sensitive credentials unrelated to the user-visible RunPod function.

Content

Scanner excerpt · scripts/_utils.py (reported line 65)May include surrounding context.

python
import boto3
    from botocore.config import Config

    secrets_path = pathlib.Path.home() / ".openclaw" / "secrets.json"
    data = json.loads(secrets_path.read_text())
    r2 = data.get("cloudflare", {}).get("r2", {})

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares no explicit tool scope despite clearly requiring filesystem, environment, and network access. In a skill system, missing scope boundaries increases the chance the agent can use broader capabilities than users or reviewers expect, weakening least-privilege controls.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill sends local images to Cloudflare R2 and then RunPod, but the description does not provide a clear user-facing warning that local files are uploaded to third-party services. Users may unknowingly expose sensitive media to external storage and AI providers.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill encourages discovery and addition of new RunPod endpoints, which lets the skill's operational scope grow over time without equivalent security review. That creates a path for unsafe or privacy-invasive endpoints to be introduced into future sessions.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Allowing call_endpoint against arbitrary endpoint IDs broadens the skill beyond its declared media role and can route prompts, files, or audio to services with different trust assumptions. The danger is amplified because users may believe they are using a narrowly scoped media tool when in fact they are invoking any compatible public endpoint.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.