Back to skill

Security audit

Pilot Leader Election

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent for leader election, but its example decision logic is under-protected enough that users should review it before relying on it for coordination.

Install only if you are prepared to harden the examples before use: restrict election group names, pass jq values with --arg, validate payload schemas, require authenticated registry participants, and do not rely on this as-is for high-impact autonomous coordination.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
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: Medium

bash
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_GROUP is 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_GROUP can modify message-selection behavior or cause the election command to fail.

Attack Path

  1. An attacker influences the value assigned to ELECTION_GROUP, such as through an environment variable, wrapper script, generated configuration, or agent-provided task parameter.
  2. The documented inbox command interpolates that value into the jq source expression.
  3. Crafted quotation marks and jq operators alter the select(...) predicate, or whitespace causes unexpected jq argum ...[truncated 680 chars]
Remediation
View remediation

Remediation Suggestions

Pass the expected topic as data through jq's --arg option 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_GROUP against a restrictive allowlist, such as ^[A-Za-z0-9._-]{1,64}$.
  • Quote every shell expansion.
  • Reject empty or malformed group identifiers.
  • Use set -euo pipefail and check jq and pilotctl exit statuses before making election decisions.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:49
Finding

Election Decisions Trust Unverified Message Payloads

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 49–56 and 82–89
Vulnerability Type: Missing message authentication, authorization, freshness, and term validation
Risk Level: Medium

bash
LAST_HEARTBEAT=$(pilotctl --json inbox \
  | jq -r '[.messages[] | select(.topic == "election:'$ELECTION_GROUP'" and .payload.type == "heartbeat")] | sort_by(.payload.timestamp) | last | .payload.timestamp')

ELAPSED=$(( $(date -u +%s) - LAST_HEARTBEAT ))

if [ "$ELAPSED" -gt 10 ]; then
  echo "Leader timeout, starting election"
fi

Candidate selection similarly trusts publisher-controlled payload fields:

bash
HIGHEST=$(pilotctl --json inbox \
  | jq -r '[.messages[] | select(.topic == "election:'$ELECTION_GROUP'" and .payload.type == "election")] | sort_by(.payload.priority) | last | .payload.candidate')

if [ "$HIGHEST" = "$AGENT_ID" ]; then
  pilotctl --json publish "$REGISTRY_HOST" "election:$ELECTION_GROUP" \
    --data "{\"type\":\"leader\",\"leader\":\"$AGENT_ID\",\"term\":$TERM}"
fi

Technical Analysis

At the skill layer, election decisions are based exclusively on topic names and attacker-controllable payload fields. The workflow does not verify that:

  • The message was signed by an authorized election-group member.
  • The authenticated sender matches the declared candidate or leader.
  • priority, timestamp, and term have valid types and permitted ranges.
  • A heartbeat belongs to the currently elected leader.
  • The term is current and monotonically increasing.
  • A message is fresh and has not been replayed.
  • Multiple declarations for the same term are rejected.

Consequently, any party permitted to inject messages into the inbox or publish to the election topic may be able to forge a high-priority candidate, impersonate a leader, or replay a heartbeat. If pilotctl or the registry independently guarantees these properties, the skill must explicitly require and ...[truncated 1758 chars]

Remediation
View remediation

Remediation Suggestions

Implement authenticated and term-aware election processing:

  1. Require cryptographic signatures or authenticated transport for every election, leader, and heartbeat message.
  2. Enforce registry ACLs so only authorized election-group members can publish to the topic.
  3. Bind the authenticated sender identity to the candidate or leader field; reject identity mismatches.
  4. Initialize TERM, persist it safely, and require terms to increase monotonically.
  5. Reject messages from older terms and conflicting leader declarations for the same term.
  6. Validate payloads against a strict schema:
    • priority, term, and timestamps must be bounded integers.
    • Candidate and leader identifiers must match an approved format.
    • Required fields must be present and unknown fields should be rejected where practical.
  7. Accept heartbeats only from the authenticated leader elected for the current term.
  8. Use a locally recorded receipt time for timeout calculations rather than trusting a publisher-provided timestamp.
  9. Include unique message identifiers or sequence numbers and retain a replay cache.
  10. Document the required authentication and authorization guarantees of pilotctl and the registry instead of assuming them.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep

Static analysis

No suspicious patterns detected.