Back to skill

Security audit

Pilot Swarm Config

Security checks for vulnerabilities and agentic risk

Overview

This skill is for shared swarm configuration, but it demonstrates automatic remote configuration updates with weak validation and unsafe local state handling that users should review before installing.

Install only if you are comfortable reviewing and tightening the examples before use: restrict who can publish config messages, verify message authenticity, validate configs against a strict schema, avoid sensitive AGENT_ID values, and store state in a private per-agent directory instead of fixed /tmp files.

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

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:109
Finding

Unauthenticated Remote Configuration Is Applied as Trusted Local State

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:45-54 and SKILL.md:109-129
Vulnerability Type: Remote configuration integrity and authorization failure
Risk Level: High

The documented subscriber treats messages as authorized configuration updates based only on their topic, payload type, and version. It does not verify a digital signature, trusted publisher identity, authorization policy, or integrity digest.

bash
LATEST_CONFIG=$(pilotctl --json inbox \
  | jq '[.messages[] | select(.topic == "config:'$SWARM_NAME'" and .payload.type == "config_update")] | sort_by(.payload.version) | last')

CONFIG_VERSION=$(echo "$LATEST_CONFIG" | jq -r '.payload.version')
CONFIG_DATA=$(echo "$LATEST_CONFIG" | jq -r '.payload.config')

echo "$CONFIG_DATA" > /tmp/swarm-config.json
echo "$CONFIG_VERSION" > /tmp/swarm-config-version.txt

The continuous subscriber also applies messages without invoking the basic validation demonstrated elsewhere:

bash
while true; do
  LATEST=$(pilotctl --json inbox \
    | jq '[.messages[] | select(.topic == "'$CONFIG_CHANNEL'" and .payload.type == "config_update")] | sort_by(.payload.version) | last')

  if [ -n "$LATEST" ] && [ "$LATEST" != "null" ]; then
    LATEST_VERSION=$(echo "$LATEST" | jq -r '.payload.version')

    if [ "$LATEST_VERSION" -gt "$CURRENT_VERSION" ]; then
      echo "Applying config version $LATEST_VERSION"
      CONFIG_DATA=$(echo "$LATEST" | jq -r '.payload.config')

      echo "$CONFIG_DATA" > /tmp/swarm-config.json
      echo "$LATEST_VERSION" > /tmp/swarm-config-version.txt

      # Report compliance
      pilotctl --json publish "$REGISTRY_HOST" "$STATUS_CHANNEL" \
        --data "{\"agent\":\"$AGENT_ID\",\"applied_version\":$LATEST_VERSION}"

      CURRENT_VERSION=$LATEST_VERSION
    fi
  fi
  sleep 5
done

Technical Analysis

A message is considered authoritative when its topic and type match and its ver ...[truncated 2118 chars]

Remediation
View remediation

Remediation Suggestions

  • Require messages to be digitally signed by an explicitly trusted configuration authority.
  • Verify the signature, publisher identity, swarm identifier, and intended environment before processing a payload.
  • Enforce registry-side authorization so only designated coordinators can publish to configuration topics.
  • Validate the complete configuration against a strict schema before replacing active state.
  • Enforce expected data types, numeric ranges, allowed keys, maximum nesting depth, and payload-size limits.
  • Reject invalid versions, duplicate versions, implausible version jumps, and updates older than an accepted freshness window.
  • Include validation in the continuous subscriber rather than presenting it as an optional, separate operation.
  • Preserve the last known-good configuration and replace active state only after authentication and validation succeed.
  • Log rejected updates and publisher identities without recording sensitive configuration values.

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:51
Finding

Predictable Shared Temporary Files Permit Symlink and Race Attacks

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:51-54, SKILL.md:77-80, and SKILL.md:121-124
Vulnerability Type: Unsafe temporary-file handling
Risk Level: High

Configuration and version data are repeatedly written to fixed, globally predictable paths using ordinary shell redirection:

bash
CONFIG_VERSION=$(echo "$LATEST_CONFIG" | jq -r '.payload.version')
CONFIG_DATA=$(echo "$LATEST_CONFIG" | jq -r '.payload.config')

echo "$CONFIG_DATA" > /tmp/swarm-config.json
echo "$CONFIG_VERSION" > /tmp/swarm-config-version.txt

The rollback operation uses the same paths:

bash
PREV_VERSION=$(echo "$PREVIOUS_CONFIG" | jq -r '.payload.version')
PREV_DATA=$(echo "$PREVIOUS_CONFIG" | jq -r '.payload.config')

echo "$PREV_DATA" > /tmp/swarm-config.json
echo "$PREV_VERSION" > /tmp/swarm-config-version.txt

The subscriber also overwrites these files directly:

bash
CONFIG_DATA=$(echo "$LATEST" | jq -r '.payload.config')

echo "$CONFIG_DATA" > /tmp/swarm-config.json
echo "$LATEST_VERSION" > /tmp/swarm-config-version.txt

Technical Analysis

/tmp is normally shared among local users. Although common systems apply a sticky bit to the directory, that protection does not make predictable filenames safe. If an attacker can create the expected paths before the subscriber does, the paths may be symbolic links to other files. Shell redirection follows symbolic links and truncates the resolved target when the subscriber has permission to write it.

The examples do not use exclusive file creation, ownership checks, symlink rejection, restrictive permissions, file locking, or atomic replacement. Multiple subscriber processes also share the same filenames, allowing different swarms or agents to overwrite each other's state. A crash between the configuration write and version write can leave inconsistent state.

Attack Path

  1. A local attacker predicts /tmp/swarm-config.json or `/tmp/s ...[truncated 1319 chars]
Remediation
View remediation

Remediation Suggestions

  • Store state in a dedicated per-agent and per-swarm directory owned by the subscriber account rather than directly under /tmp.
  • Create the directory with mode 0700 and verify its owner and type before every use.
  • Set umask 077 before creating files containing configuration or state.
  • Generate temporary files securely with mktemp inside the trusted directory.
  • Open files with exclusive-creation and no-follow semantics where the implementation language permits it.
  • Verify that destination paths are regular files owned by the expected account and reject symbolic links.
  • Write the complete configuration to a temporary file, validate it, synchronize it if durability is required, and atomically rename it into place.
  • Use a lock to serialize readers, updates, and rollbacks.
  • Include the swarm and agent identifiers in sanitized filenames or use separate trusted subdirectories.
  • Update configuration and version metadata as one atomic state object to avoid mismatched files after crashes.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:45
Finding

Untrusted Values Are Interpolated Directly into jq Programs

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:45-47, SKILL.md:71-73, and SKILL.md:109-112
Vulnerability Type: jq expression injection and unsafe shell interpolation
Risk Level: Medium

Swarm names, channel names, and version values are inserted directly into jq source code:

bash
LATEST_CONFIG=$(pilotctl --json inbox \
  | jq '[.messages[] | select(.topic == "config:'$SWARM_NAME'" and .payload.type == "config_update")] | sort_by(.payload.version) | last')

The rollback filter also embeds a shell variable as jq syntax:

bash
CURRENT_VERSION=$(cat /tmp/swarm-config-version.txt)
PREVIOUS_CONFIG=$(pilotctl --json inbox \
  | jq '[.messages[] | select(.topic == "config:'$SWARM_NAME'" and .payload.type == "config_update" and .payload.version < '$CURRENT_VERSION')] | sort_by(.payload.version) | last')

The continuous subscriber uses the same construction:

bash
LATEST=$(pilotctl --json inbox \
  | jq '[.messages[] | select(.topic == "'$CONFIG_CHANNEL'" and .payload.type == "config_update")] | sort_by(.payload.version) | last')

Technical Analysis

The variables are treated as fragments of a jq program rather than data arguments. A value containing quotation marks, jq operators, filters, or delimiters can terminate the intended string or expression and alter the resulting filter. Unquoted shell expansions also undergo word splitting and pathname expansion before jq is invoked.

This is jq expression injection, not direct shell command injection: the shown interpolation does not place attacker content into a shell command-substitution construct evaluated by the shell. However, a crafted value can change which messages are selected, make validation ineffective, cause syntax errors, or consume excessive processing resources.

CURRENT_VERSION is especially sensitive because it is read from a mutable file and inserted as a raw jq expression. A malformed or attacker-modified versi ...[truncated 1276 chars]

Remediation
View remediation

Remediation Suggestions

Pass all variable values to jq as data using --arg or --argjson rather than concatenating them into the filter:

bash
LATEST_CONFIG=$(pilotctl --json inbox |
  jq --arg topic "config:$SWARM_NAME" \
    '[.messages[] |
      select(.topic == $topic and .payload.type == "config_update")] |
     sort_by(.payload.version) |
     last')

Parse and validate the version as a decimal integer before using it:

bash
case "$CURRENT_VERSION" in
  ''|*[!0-9]*) echo "Invalid configuration version" >&2; exit 1 ;;
esac

PREVIOUS_CONFIG=$(pilotctl --json inbox |
  jq --arg topic "config:$SWARM_NAME" \
     --argjson current "$CURRENT_VERSION" \
    '[.messages[] |
      select(.topic == $topic and
             .payload.type == "config_update" and
             .payload.version < $current)] |
     sort_by(.payload.version) |
     last')
  • Quote every shell variable expansion unless deliberate splitting is required.
  • Restrict swarm and channel identifiers to a documented allowlist of characters and a reasonable maximum length.
  • Treat the local version file as untrusted input and reject malformed, negative, fractional, or excessively large values.
  • Enable strict shell behavior where appropriate, such as set -euo pipefail, while handling expected empty results explicitly.
  • Add tests containing quotes, backslashes, whitespace, jq operators, and malformed version data.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (2)

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

This markdown skill includes commands that overwrite /tmp/swarm-config.json and /tmp/swarm-config-version.txt, which are file-write operations affecting local state. While the commands are shown as examples, the surrounding documentation does not explicitly warn users that applying or rolling back configuration will overwrite local files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

The compliance-tracking example publishes AGENT_ID and applied version data to a registry host, which is a network transmission of system/agent metadata. The markdown describes the workflow but does not explicitly warn that agent identity and configuration status are sent over the network.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.