Back to skill

Security audit

SecretClaw

Security checks for vulnerabilities and agentic risk

Overview

The skill is not overtly malicious, but it handles secrets through a public Cloudflare tunnel and has web-form injection flaws that could expose a submitted secret if invocation inputs are manipulated.

Install only if you are comfortable sending secrets through a temporary Cloudflare-managed tunnel and you trust the agent invocation to choose the exact config key and label. Until fixed, avoid using it for high-value credentials; the form should escape all dynamic HTML, validate config keys, hide raw subprocess errors, and clearly warn users about Cloudflare tunnel transit.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/secret_server.py:99
Finding
Unescaped HTML Injection in the Secret Entry Interface<![CDATA[ ## Vulnerability Details **File Location**: `scripts/secret_server.py`, lines 99–121 **Additional Sink**: `scripts/secret_server.py`, line 167 **Vulnerability Type**: HTML injection and potential cross-site scripting through unescaped command-line arguments and subprocess error output **Risk Level**: High ### Vulnerable Code ```python HTML_FORM = f"""<!DOCTYPE html> <html><head><meta charset="utf-8"><title>Enter {label}</title> <style> body {{ font-family: -apple-system, sans-serif; max-width: 480px; margin: 80px auto; padding: 20px; background: #f5f5f5; }} .card {{ background: white; padding: 32px; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,.1); }} h2 {{ margin-top: 0; color: #333; }} input {{ width: 100%; padding: 12px; font-size: 15px; margin: 12px 0; box-sizing: border-box; border: 1px solid #ddd; border-radius: 8px; font-family: monospace; }} button {{ background: #5865F2; color: white; border: none; padding: 14px; font-size: 16px; border-radius: 8px; cursor: pointer; width: 100%; margin-top: 8px; }} button:hover {{ background: #4752c4; }} .note {{ color: #888; font-size: 13px; margin-top: 12px; }} </style></head> <body><div class="card"> <h2>🔑 Enter {label}</h2> <p>Your value will be saved immediately and this server will shut down automatically.<br>Nothing is stored in chat history.</p> <form method="POST" action="/submit?token={token}"> <input type="password" name="value" placeholder="Enter your value" autocomplete="off" autofocus required> <button type="submit">Save</button> </form> <p class="note">Config path: <code>{config_key}</code></p> </div></body></html>""" ``` A second unescaped HTML sink exposes subprocess error output: ```python self._respond(500, f"<h2>Save failed</h2><pre>{result.stderr}</pre>") ``` ### Technical Analysis The server constructs HTML using Python f-strings and inserts `label` and `config_key` directly into markup without contextual HTML escaping. Both values originate from comm ...[truncated 3238 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every dynamic value before inserting it into HTML: ```python import html safe_label = html.escape(label, quote=True) safe_config_key = html.escape(config_key, quote=True) ``` Use only the escaped values in element text, attributes, and page titles. 2. Do not render raw subprocess errors in HTTP responses. Return a fixed generic message to the browser and write sanitized diagnostic details only to a protected local log: ```python self._respond(500, HTML_SAVE_FAILED) print("ERROR: configuration update failed", file=sys.stderr, flush=True) ``` 3. Strictly validate `config_key`. If OpenClaw keys use dot notation, apply an allowlist appropriate to that grammar, for example: ```python if not re.fullmatch(r"[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*", config_key): parser.error("Invalid config key") ``` The exact grammar should match OpenClaw's documented configuration-path syntax. 4. Restrict `label` to a reasonable length and reject control characters. HTML escaping remains required even after validation. 5. Add a restrictive Content Security Policy as defense in depth, for example: ```python self.send_header( "Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; " "base-uri 'none'; frame-ancestors 'none'" ) ``` Where practical, move CSS to a static resource or use a nonce so that `style-src 'unsafe-inline'` is unnecessary. 6. Add security-focused tests using labels, configuration keys, and error strings containing characters such as `<`, `>`, `"`, `'`, and `&`. Verify that they are rendered as text and cannot create elements, attributes, or scripts. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises and instructs use of capabilities including shell execution, local file reads/writes, network access, and environment/config manipulation, but declares no explicit tool scope or permission boundaries. In a secret-handling skill, this is dangerous because the agent may invoke more privileges than necessary, making accidental secret exposure, config tampering, or unintended network transmission harder to constrain or audit.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill presents the flow as 'secure' but does not prominently warn users that secrets are transmitted through a Cloudflare Quick Tunnel, meaning the value leaves the local machine and traverses third-party infrastructure. This omission can mislead users into believing the process is equivalent to purely local secret entry, reducing informed consent around confidentiality and trust boundaries.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
Active tunnel info is recorded in `workspace/TUNNELS.md`.
The agent reads this file to check currently open tunnel URLs.
Entries are automatically removed when the server shuts down.

## Security
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
def update_tunnels_md(action: str, service: str, port: int, url: str, token: str, label: str):
    """Add or remove tunnel info from TUNNELS.md."""
    if not TUNNELS_MD.exists():
        TUNNELS_MD.write_text("# TUNNELS.md — Active Tunnels\n\nActive Cloudflare tunnels managed by the agent.\nAutomatically removed on server shutdown.\n\n| Service | Port | URL | Description |\n|---------|------|-----|-------------|\n")

    content = TUNNELS_MD.read_text()
Confidence
80% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def start_cloudflare_tunnel(port: int) -> tuple[subprocess.Popen, str]:
    """Start a cloudflared tunnel and return the public URL."""
    proc = subprocess.Popen(
        ["cloudflared", "tunnel", "--url", f"http://localhost:{port}"],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill’s core design sends user secrets through a Cloudflare Tunnel to an internet-reachable endpoint, yet the UI text says only that the value is not stored in chat history and does not clearly warn that a third party proxies the secret. In this context, that is materially dangerous because the skill is specifically marketed for API keys, tokens, and passwords, so users may assume purely local handling when their secrets are actually transmitted through external infrastructure.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
body = urllib.parse.parse_qs(self.rfile.read(length).decode())
                value = body.get("value", [""])[0].strip()
                if value:
                    result = subprocess.run(
                        ["openclaw", "config", "set", config_key, value],
                        capture_output=True, text=True
                    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Vague Triggers

Low
Confidence
89% confidence
Finding
The description says to use the skill when registering API keys, tokens, passwords, or any sensitive config values, but it does not define explicit trigger phrases, constraints, or exclusion cases. This broad wording could overlap with many generic secret-handling situations and makes it unclear when this skill should be invoked instead of other configuration workflows.

Static analysis

No suspicious patterns detected.