T09 · Insecure Skill Coding Practices
- Location
SKILL.md:49- Finding
Election Group Interpolation Allows jq Filter Injection
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 49–51 and 82–84
Vulnerability Type: Unsafe variable interpolation into executable jq expressions
Risk Level: Mediumbash LAST_HEARTBEAT=$(pilotctl --json inbox \ | jq -r '[.messages[] | select(.topic == "election:'$ELECTION_GROUP'" and .payload.type == "heartbeat")] | sort_by(.payload.timestamp) | last | .payload.timestamp')The same vulnerable construction is used when selecting election candidates:
bash HIGHEST=$(pilotctl --json inbox \ | jq -r '[.messages[] | select(.topic == "election:'$ELECTION_GROUP'" and .payload.type == "election")] | sort_by(.payload.priority) | last | .payload.candidate')Technical Analysis
ELECTION_GROUPis expanded by the shell directly into the source text of a jq filter rather than being passed as a jq data argument. A value containing jq syntax or quotation marks can terminate or modify the intended string expression. Because the expansion is also unquoted at the shell level, whitespace and wildcard characters can trigger word splitting and pathname expansion.This construction permits jq expression injection and can also cause additional command arguments to be supplied to jq. Shell metacharacters introduced solely through variable expansion are not reparsed as shell control operators, so direct operating-system command execution is not established by this snippet. However, an attacker who can influence
ELECTION_GROUPcan modify message-selection behavior or cause the election command to fail.Attack Path
- An attacker influences the value assigned to
ELECTION_GROUP, such as through an environment variable, wrapper script, generated configuration, or agent-provided task parameter. - The documented inbox command interpolates that value into the jq source expression.
- Crafted quotation marks and jq operators alter the
select(...)predicate, or whitespace causes unexpected jq argum ...[truncated 680 chars]
- An attacker influences the value assigned to
- Remediation
View remediation
Remediation Suggestions
Pass the expected topic as data through jq's
--argoption instead of constructing jq source code dynamically:bash TOPIC="election:$ELECTION_GROUP" LAST_HEARTBEAT=$(pilotctl --json inbox \ | jq -r --arg topic "$TOPIC" \ '[.messages[] | select(.topic == $topic and .payload.type == "heartbeat")] | sort_by(.payload.timestamp) | last | .payload.timestamp')Apply the same pattern to candidate selection:
bash HIGHEST=$(pilotctl --json inbox \ | jq -r --arg topic "$TOPIC" \ '[.messages[] | select(.topic == $topic and .payload.type == "election")] | sort_by(.payload.priority) | last | .payload.candidate')In addition:
- Validate
ELECTION_GROUPagainst a restrictive allowlist, such as^[A-Za-z0-9._-]{1,64}$. - Quote every shell expansion.
- Reject empty or malformed group identifiers.
- Use
set -euo pipefailand check jq andpilotctlexit statuses before making election decisions.
- Validate
