T05 · Unauthorized Access and Privilege Escalation
- Location
SKILL.md:189- Finding
Direct Access to Telegram Bot Credentials Bypasses the Protected Messaging Abstraction
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
SKILL.md:189Direct Access to Telegram Bot Credentials Bypasses the Protected Messaging Abstraction
scripts/_utils.py:59Arbitrary Readable Local Files Can Be Uploaded to Cloudflare R2
scripts/_utils.py:158Untrusted Endpoint Response URLs Are Downloaded Without Network or Resource Restrictions
scripts/run.sh:17Runtime Dependencies Are Unpinned and Resolved During Skill Execution
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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":
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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":
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
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")
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.
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.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
## API Keys
One key required — add to `~/.openclaw/secrets.json`:
| Key path | Purpose | Get it from |
|----------|---------|-------------|
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.
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
### 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:
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.
Read the bot token from secrets and send via curl — this always works regardless of sandbox mode:
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 \
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.
def _r2_config() -> dict:
"""Load R2 config from secrets.json."""
cfg = _from_secrets_json("/cloudflare/r2/accessKeyId")
if not cfg:
# Return full dict
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.
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 {}
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.
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", {})
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.
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.
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.
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.
No suspicious patterns detected.