Back to skill

Security audit

Pilot Heartbeat Monitor

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed heartbeat-monitoring helper, but its sample shell commands should be hardened before use in a real failover system.

Install only if you intend to use pilotctl-based swarm health monitoring. Before deploying the examples for automatic failover or leader election, update the shell snippets to pass dynamic values into jq with --arg, read agent IDs safely, validate peer identifiers and addresses, and add explicit error handling.

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 (1)

T09 · Insecure Skill Coding Practices

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

Vulnerable 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
done

Technical Analysis

Although the command passes time values safely through jq --arg, it directly concatenates $SWARM_NAME into the first jq program and $agent into the second. Values inserted this way are interpreted as jq syntax rather than exclusively as data.

The agent identifier is derived from .payload.agent in 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_AGENTS performs shell word splitting and pathname expansion. The command producing FAILED_AGENTS does 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 pipefail and explicit error handling so pilotctl or jq failures cannot silently produce incorrect failover decisions.
  • Avoid whitespace-delimited transport for structured identifiers; use line-delimited raw output or another structured representation.
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.