T09 · Insecure Skill Coding Practices
- Location
SKILL.md:43- Finding
Untrusted values are interpolated into jq programs
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 43–56
Vulnerability Type: jq query injection and unsafe shell word splitting
Risk Level: MediumVulnerable Code
bash FAILED_AGENTS=$(pilotctl --json inbox \ | jq --arg now "$CURRENT_TIME" --arg timeout "$TIMEOUT" \ '[.messages[] | select(.topic == "heartbeat:'$SWARM_NAME'") | {agent: .payload.agent, last_seen: .payload.timestamp}] | group_by(.agent) | map(select(($now | tonumber) - (map(.last_seen) | max) > ($timeout | tonumber))) | .[].agent') for agent in $FAILED_AGENTS; do AGENT_ADDR=$(pilotctl --json peers | jq -r '.[] | select(.node_id == "'$agent'") | .address') PING_RESULT=$(pilotctl --json ping "$AGENT_ADDR" --count 3 --timeout 2s) LOSS=$(echo "$PING_RESULT" | jq -r '.packet_loss_pct') if [ "$LOSS" = "100" ]; then echo "Agent $agent CONFIRMED DOWN" fi doneTechnical Analysis
Although the command passes time values safely through
jq --arg, it directly concatenates$SWARM_NAMEinto the first jq program and$agentinto the second. Values inserted this way are interpreted as jq syntax rather than exclusively as data.The agent identifier is derived from
.payload.agentin heartbeat messages and may therefore be controlled by a malicious or compromised peer. A specially formed identifier can corrupt or alter the peer-selection expression. In addition,for agent in $FAILED_AGENTSperforms shell word splitting and pathname expansion. The command producingFAILED_AGENTSdoes not use jq's raw-output option, so even ordinary string identifiers retain JSON quoting, potentially generating malformed jq expressions.The shell does not reinterpret command substitutions or shell metacharacters introduced through variable expansion as new shell syntax, so this code does not by itself demonstrate arbitrary shell-command execution. The confirmed risks are query manipulation, incorrect addres ...[truncated 1435 chars]
- Remediation
View remediation
Remediation Suggestions
Pass every dynamic value to jq as data rather than concatenating it into jq source:
bash FAILED_AGENTS=$( pilotctl --json inbox | jq -r \ --arg swarm "$SWARM_NAME" \ --argjson now "$CURRENT_TIME" \ --argjson timeout "$TIMEOUT" \ ' [.messages[] | select(.topic == ("heartbeat:" + $swarm)) | { agent: .payload.agent, last_seen: (.payload.timestamp | tonumber) } ] | group_by(.agent) | map(select($now - (map(.last_seen) | max) > $timeout)) | .[].agent ' ) while IFS= read -r agent; do [ -n "$agent" ] || continue AGENT_ADDR=$( pilotctl --json peers | jq -r --arg agent "$agent" \ '.[] | select(.node_id == $agent) | .address' ) [ -n "$AGENT_ADDR" ] && [ "$AGENT_ADDR" != "null" ] || continue PING_RESULT=$(pilotctl --json ping "$AGENT_ADDR" --count 3 --timeout 2s) LOSS=$(printf '%s\n' "$PING_RESULT" | jq -r '.packet_loss_pct') if [ "$LOSS" = "100" ]; then printf 'Agent %s CONFIRMED DOWN\n' "$agent" fi done <<< "$FAILED_AGENTS"Additionally:
- Validate agent identifiers against the protocol's documented identifier format and reject unexpected characters or excessive lengths.
- Validate that each resolved address belongs to an approved host or network range before probing it.
- Treat malformed heartbeat payloads as invalid rather than allowing them to terminate the monitoring loop.
- Use
set -o pipefailand explicit error handling sopilotctlor jq failures cannot silently produce incorrect failover decisions. - Avoid whitespace-delimited transport for structured identifiers; use line-delimited raw output or another structured representation.
