T09 · Insecure Skill Coding Practices
- Location
opencode-session.sh:344- Finding
Unescaped User Input in Agent-Executable Workflow Output
- Content
View full analysis
Vulnerability Details
File Location:
opencode-session.sh, lines 344-358
Vulnerability Type: Command and JSON-RPC instruction injection
Risk Level: HighVulnerable Code
bash ## Step 1: Start OpenCode exec(command: "opencode acp --cwd $project", background: true, workdir: "$project") # → Save returned sessionId as PROCESS_SESSION_ID ## Step 2: Initialize process.write(PROCESS_SESSION_ID, data: '$(initialize_opencode)' + "\\n") process.poll(PROCESS_SESSION_ID, timeout: $TIMEOUT_INIT) # → Expect: {"result":{"protocolVersion":1,...}} ## Step 3: Create Session process.write(PROCESS_SESSION_ID, data: '$(create_session "$project" "$mcp_servers")' + "\\n") process.poll(PROCESS_SESSION_ID, timeout: $TIMEOUT_SESSION) # → Save result.sessionId as OPENCODE_SESSION_ID ## Step 4: Send Prompt process.write(PROCESS_SESSION_ID, data: '$(send_prompt "OPENCODE_SESSION_ID" "$prompt")' + "\\n")The underlying JSON-RPC construction also directly interpolates these values:
bash create_session() { local cwd="$1" local mcp_servers="$2" log INFO "Creating session in $cwd" local params="{\"cwd\":\"${cwd}\",\"mcpServers\":${mcp_servers:-[]}}" local json=$(send_jsonrpc "session/new" "$params") echo "$json" } send_prompt() { local session_id="$1" local prompt="$2" log INFO "Sending prompt (${#prompt} chars)" local params="{\"sessionId\":\"${session_id}\",\"prompt\":[{\"type\":\"text\",\"text\":\"${prompt}\"}]}" local json=$(send_jsonrpc "session/prompt" "$params") echo "$json" }Technical Analysis
Values supplied through
--project,--prompt, and--mcpare inserted into generated command syntax and JSON-RPC messages without context-appropriate escaping or validation.The script does not directly execute the generated workflow. However, its documentation explicitly instructs CYPHER or another consuming age ...[truncated 1919 chars]
- Remediation
View remediation
Remediation Suggestions
-
Stop producing executable command syntax through string interpolation. Return a structured data document whose fields remain data rather than instructions.
-
Construct all JSON-RPC messages with a real JSON serializer, such as:
bash jq -cn \ --arg cwd "$cwd" \ --argjson mcpServers "$mcp_servers" \ '{jsonrpc:"2.0", id:1, method:"session/new", params:{cwd:$cwd, mcpServers:$mcpServers}}' -
Construct prompt messages with
jq --arg prompt "$prompt"so quotation marks, newlines, backslashes, and control characters are escaped correctly. -
Validate MCP input before use:
bash jq -e 'type == "array" and all(.[]; type == "string")' \ <<< "$mcp_servers" >/dev/null -
Canonicalize the project path with
realpath, require it to be an existing directory, and optionally restrict it to approved workspace roots. -
Invoke processes through structured argument arrays rather than a generated shell command string.
-
Ensure the downstream agent treats script output as untrusted data. It should execute only a fixed, locally defined workflow and populate validated parameters into that workflow.
-
Add regression tests covering embedded quotation marks, newlines, backslashes, JSON delimiters, and strings resembling agent tool calls.
-
