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. ]]>
