T09 · Insecure Skill Coding Practices
Error
- Location
- references/tavily-curl-example.md:34
- Finding
- Shell Command Injection Through Unescaped Search Queries<![CDATA[ ## Vulnerability Details **File Location**: `references/tavily-curl-example.md`, lines 34-53 **Vulnerability Type**: Shell command injection and unsafe secret handling **Risk Level**: High ### Vulnerable Code ```python key = terminal("grep TAVI ~/.openclaw/.env | cut -d'=' -f2", timeout=5)['output'].strip() queries = [ "候选建议1", "候选建议2", ] for q in queries: r = terminal( f'''curl -s "https://api.tavily.com/search" -H "Content-Type: application/json" -d '{{ "api_key": "{key}", "query": "{q}", "search_depth": "basic", "max_results": 3 }}' | python3 -c "import sys,json; d=json.load(sys.stdin); [print(r['title']+' | '+r['url'][:60]) for r in d.get('results',[])]"''', timeout=15 ) status = "🚫" if r['output'].strip() else "✅" print(f"{status} | {q}") ``` ### Technical Analysis The batch-search example interpolates `q` directly into a command string passed to `terminal`, which invokes a shell. The query is placed inside a single-quoted JSON argument without shell escaping or safe JSON serialization. A query containing a single quote can terminate the JSON argument. Subsequent shell metacharacters, such as `;`, `|`, command substitution, or redirection, can then be interpreted by the shell. Escaping for JSON alone would not be sufficient because shell parsing occurs separately. The Tavily API key is also interpolated into the command line. Depending on the terminal implementation and operating system, the resulting command may be exposed through process inspection, command logging, debugging output, or exception reports. Although the example currently contains static placeholder queries, it is explicitly designed as a reusable batch-search integration. It becomes exploitable when query values are derived from user input, generated counterpart names, search terms, or other untrusted content. ### Attack Path 1. An attacker supplies or causes the Skill to g ...[truncated 1360 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not construct shell commands from query text or credentials. 2. Replace `terminal` and `curl` with a native HTTP client such as `urllib.request` or a vetted Python HTTP library. 3. Build the request body using `json.dumps` rather than string interpolation. 4. Obtain the credential from the process environment instead of parsing a shared environment file: ```python import json import os import urllib.request key = os.environ["TAVILY_API_KEY"] for q in queries: if not isinstance(q, str): raise TypeError("Search query must be a string") if len(q) > 1000: raise ValueError("Search query is too long") payload = json.dumps({ "api_key": key, "query": q, "search_depth": "basic", "max_results": 3, }).encode("utf-8") request = urllib.request.Request( "https://api.tavily.com/search", data=payload, headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(request, timeout=15) as response: result = json.load(response) ``` 5. Where supported by the API, transmit credentials in an authorization header rather than in the JSON body. 6. Ensure logs and error messages redact credentials. 7. If an external program must be used, invoke it with an argument array and without a shell. Do not place secrets directly in command-line arguments. 8. Add tests containing quotes, semicolons, command substitution syntax, newlines, and Unicode control characters to confirm that query values cannot alter command execution. ]]>
