T09 · Insecure Skill Coding Practices
- Location
SKILL.md:59- Finding
Shell Command Injection Through Unsafe Prompt Substitution
- Content
View full analysis
/dev/null | jq -r '.profiles[.defaultProfile].apiKey' 2>/dev/null)}" && \ curl -s -X POST "https://oatda.com/api/v1/compare" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OATDA_API_KEY" \ -d '{ "prompt": "", "models": [ {"provider": "openai", "modelId": "gpt-5"}, {"provider": "anthropic", "modelId": "claude-sonnet-4-5-20250929"}, {"provider": "google", "modelId": "gemini-3-pro-preview"} ], "temperature": 0.7, "maxTokens": 1024, "stream": false }' ``` The subsequent instruction states: ```text Replace `` with the user's prompt (JSON-escape special characters). ``` ### Technical Analysis The Skill instructs the Agent to replace `` inside a shell command whose HTTP request body is enclosed in shell single quotes. Although the documentation tells the Agent to JSON-escape special characters, JSON escaping does not protect a value embedded directly into shell source. In particular, a single quote in an attacker-controlled prompt can terminate the shell's single-quoted `-d` argument. Text following that quote can then be interpreted as shell syntax rather than JSON data. Shell metacharacters such as semicolons, redirections, command substitutions, and comment markers may consequently cause additional commands to execute. The underlying security failure is the use of textual substitution to combine untrusted data and executable shell syntax. JSON encoding and shell argument encoding are distinct operations; satisfying the former does not satisfy the latter. ### Attack Path 1. An attacker asks the Agent to compare models using a prompt containing a single ...[truncated 1674 chars]- Remediation
View remediation
/dev/null)}" payload=$( jq -n --arg prompt "$USER_PROMPT" '{ prompt: $prompt, models: [ {provider: "openai", modelId: "gpt-5"}, {provider: "anthropic", modelId: "claude-sonnet-4-5-20250929"}, {provider: "google", modelId: "gemini-3-pro-preview"} ], temperature: 0.7, maxTokens: 1024, stream: false }' ) curl --silent --show-error --fail-with-body \ -X POST "https://oatda.com/api/v1/compare" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OATDA_API_KEY" \ --data-binary "$payload" ``` Additional hardening measures: 1. Obtain the prompt through a variable or structured tool parameter rather than generating shell code containing it. 2. Prefer a structured HTTP client or Agent HTTP tool that accepts headers and JSON values separately. 3. Never use `eval`, command substitution, or dynamically generated shell scripts to process the prompt. 4. Validate that the API key is neither empty nor the literal value `null` before issuing a request. 5. Run the Skill in a sandbox with minimum filesystem and network permissions. 6. Restrict access to `~/.oatda/credentials.json` to the owning user, such as mode `0600`. 7. Avoid displaying even partial API-key prefixes unless operationally necessary, as prefixes may still constitute sensitive metadata. 8. Add regression tests using prompts containing single quotes, newlines, command substitutions, semicolons, redirections, and other shell metacharacters. ]]>
