Back to skill

Security audit

A2a Register

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says at a high level, but it uses registry-wide admin access and unsafe shell/Python handling that could expose credentials or allow command execution if inputs or config are tampered with.

Review carefully before installing. Use only with a trusted gateway over HTTPS, avoid untrusted or shared a2a.conf files, restrict file permissions manually, and do not pass or store agent slugs or config values from untrusted sources until the shell/Python injection issues are fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
register.sh:84
Finding

Arbitrary Python Code Execution Through Unsafely Interpolated Agent Slug

Content
View full analysis

Vulnerability Details

File Location: register.sh:84-99, deregister.sh:63-77, heartbeat.sh:62-76, and status.sh:81-111
Vulnerability Type: Python source-code injection
Risk Level: High

Vulnerable Code

The same vulnerable construction appears in multiple scripts. For example, register.sh contains:

bash
EXISTING_ID=$(echo "$EXISTING_BODY" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for a in agents:
            if a.get('slug') == slug:
                print(a.get('id', ''))
                break
except: pass
" 2>/dev/null)

Equivalent direct interpolation occurs in:

bash
AGENT_ID=$(echo "$EXISTING" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for a in agents:
            if a.get('slug') == slug:
                print(a.get('id', ''))
                break
except: pass
" 2>/dev/null)

Technical Analysis

AGENT_SLUG_VAL can originate from a command-line --slug argument, the AGENT_SLUG environment variable, or the sourced a2a.conf file. The scripts insert that value directly into the source passed to python3 -c.

Shell quoting does not make this safe because the attacker-controlled value becomes part of the generated Python program. A value containing a single quote, Python statement separators, and additional Python expressions can terminate the intended string assignment and inject new statements. Python then executes those statements with the privileges of the user running the Skill.

The broad except blocks do not mitigate this issue. Injected statements are evaluated while Python parses and executes the ...[truncated 1126 chars]

Remediation
View remediation

Remediation Suggestions

Never interpolate configuration or argument values into Python source. Pass the slug as a positional argument:

bash
EXISTING_ID=$(printf '%s' "$EXISTING_BODY" | python3 -c '
import sys, json

slug = sys.argv[1]
try:
    data = json.load(sys.stdin)
    agents = data.get("data", data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for agent in agents:
            if agent.get("slug") == slug:
                print(agent.get("id", ""))
                break
except (json.JSONDecodeError, TypeError, AttributeError):
    pass
' "$AGENT_SLUG_VAL")

Apply the same correction to deregister.sh, heartbeat.sh, and status.sh. Additionally:

  • Validate slugs against an explicit allowlist, such as ^[a-z0-9][a-z0-9-]{0,62}$.
  • Reject control characters, quotes, whitespace, and unexpected punctuation.
  • Avoid broad exception suppression and report parsing failures safely.
  • Add regression tests using slugs containing quotes, newlines, semicolons, and Unicode edge cases.

T09 · Insecure Skill Coding Practices

Warning
Location
a2a-setup.sh:116
Finding

Executable Shared Configuration Enables Persistent Shell Command Injection and Exposes Secrets

Content
View full analysis

Vulnerability Details

File Location: a2a-setup.sh:60, a2a-setup.sh:116-146, register.sh:9-12, deregister.sh:9-12, heartbeat.sh:9-12, and status.sh:9-12
Vulnerability Type: Unsafe configuration sourcing, shell command injection, and insecure secret storage
Risk Level: Medium

Vulnerable Code

a2a-setup.sh loads an existing configuration as executable shell code:

bash
if [[ -f "$CONF_FILE" ]]; then
    echo "📝 Existing config found — current values shown as defaults."
    # shellcheck source=a2a.conf
    source "$CONF_FILE"
fi

It writes unescaped values, including an API key, into shell syntax:

bash
cat > "$CONF_FILE" <<EOF
# A2A Configuration — auto-generated by a2a-setup.sh or manually edited
# This file is shared by a2a-client, a2a-server, and a2a-register skills.
# Priority order: CLI flags > env vars > a2a.conf > auto-detected defaults
#
# REQUIRED: Set A2A_GATEWAY_URL to your A2A API Gateway instance
# REQUIRED: Set A2A_GATEWAY_API_KEY for task auth (or leave empty to disable auth checks)

A2A_GATEWAY_URL="${GW_URL}"
A2A_GATEWAY_API_KEY="${GW_API_KEY}"

# Agent identity — auto-detected from hostname if empty
AGENT_NAME="${AGENT_NAME_VAL}"
AGENT_SLUG="${AGENT_SLUG_VAL}"

# Agent endpoint — auto-detected from Tailscale/local IP if empty
AGENT_URL="${AGENT_URL_VAL}"

# Agent capabilities (comma-separated)
AGENT_CAPABILITIES="${AGENT_CAPS}"

# Listener bind address — auto-detected from Tailscale/local IP if empty
BIND_ADDR=""

# Listener auth type
AGENT_AUTH_TYPE="${AGENT_AUTH_VAL:-bearer}"

# Listener port
LISTENER_PORT="${LISTENER_PORT}"
EOF

Every management script subsequently executes the file:

bash
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

Technical Analysis

The configuration file is treated as a trusted shell program rather than data. Values supplied inte ...[truncated 2068 chars]

Remediation
View remediation

Remediation Suggestions

Replace the executable shell configuration with a non-executable data format such as JSON. Parse it with a strict parser and assign only explicitly recognized fields.

If shell-compatible configuration must temporarily remain:

  • Serialize every value with printf '%q' instead of inserting raw values into a heredoc.
  • Validate each field against an appropriate allowlist.
  • Reject newlines, carriage returns, NUL bytes, command substitutions, and unexpected shell metacharacters.
  • Set umask 077 before creating the configuration.
  • Create files atomically and enforce mode 0600.
  • Verify that the file is a regular file, is owned by the expected user, and is not writable by group or other users before loading it.
  • Avoid source; use a parser that treats the file strictly as data.
  • Store secrets in a dedicated credential store or protected secret file rather than alongside ordinary configuration.
  • Audit and restrict permissions on the shared a2a-client directory.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
register.sh:56
Finding

Registry-Wide Admin JWT Is Retrieved and Transmitted Over Optionally Plaintext Connections

Content
View full analysis

Vulnerability Details

File Location: register.sh:56-78, deregister.sh:44-65, heartbeat.sh:45-64, status.sh:45-65, and documented HTTP examples in SKILL.md
Vulnerability Type: Excessive privilege and insecure transmission of bearer credentials
Risk Level: Medium

Vulnerable Code

Each management script obtains an admin token from the configured gateway without using the accepted API-key value:

bash
echo "Obtaining admin JWT from gateway..."
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

The resulting bearer token is then used to list all agents:

bash
EXISTING=$(curl -sS -w "\n%{http_code}" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

The documentation explicitly permits and demonstrates plaintext gateway URLs:

bash
A2A_GATEWAY_URL=http://GATEWAY_IP:8090 A2A_GATEWAY_API_KEY=your-key ./a2a-setup.sh --non-interactive

Technical Analysis

Heartbeat, status, and deregistration of one agent should require only permissions scoped to that specific agent record. Instead, every operation retrieves an admin JWT and calls an endpoint that lists the entire registry. This exceeds the minimum privileges required for the declared functionality.

The scripts accept --api-key and load A2A_GATEWAY_API_KEY, but the value is not used to authenticate the bootstrap request or subsequent admin calls. Security therefore depends on the gateway's bootstrap endpoint and network placement.

Because GATEWAY_URL may use http://, neither the bootstrap response nor later bearer-token requests are protected agai ...[truncated 1537 chars]

Remediation
View remediation

Remediation Suggestions

  • Require https:// gateway URLs and reject plaintext HTTP except for an explicitly enabled loopback-only development mode.
  • Keep normal TLS certificate and hostname verification enabled.
  • Authenticate bootstrap requests; do not expose administrative tokens through an unauthenticated bootstrap endpoint.
  • Replace the admin JWT with a token scoped to the current agent and operation.
  • Provide direct endpoints such as GET /agents/self, PATCH /agents/self/heartbeat, and DELETE /agents/self.
  • Avoid listing every registered agent merely to locate the current record.
  • Use the configured API key if it is the intended bootstrap credential, or remove the unused option to avoid a false sense of protection.
  • Apply short token lifetimes, audience restrictions, operation-specific scopes, and server-side revocation.
  • Do not print bearer tokens or include them in command-line arguments.
  • Update all documentation examples to use authenticated HTTPS endpoints.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

There is a clear description/behavior mismatch. The declared purpose is operational lifecycle management of agent registration in the A2A gateway. The actual code only performs local configuration setup by generating a2a.conf. Although this configuration may support later registration, the code itself never contacts the gateway, invokes register/deregister/status APIs, or sends heartbeats. Its primary purpose is setup/configuration, not gateway registration management.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The description presents a broader lifecycle-management skill covering registration, deregistration, heartbeat, and status checks. The supplied code chunk implements only registration/update logic. It does not include any path for deregistering an agent, sending heartbeat signals, or querying status independently. The extra use of admin bootstrap and agent enumeration is consistent with supporting registration, so by itself that would not be a serious mismatch; however, the omission of most declared management functions makes the declared description materially broader than the actual code behavior.

Content

No source excerpt is available for this finding.

External Script Fetching

High
Category
Supply Chain
Confidence
97% confidence
Finding

The script obtains an admin JWT by calling a bootstrap endpoint with no visible authentication or trust validation beyond whatever curl accepts for the URL, and then uses that token for privileged admin API access. If the gateway URL is misconfigured, malicious, or served over insecure transport, the script could hand control to an attacker who returns a forged token or captures privileged workflow metadata, enabling unauthorized administrative operations against the agent registry.

Content

Scanner excerpt · heartbeat.sh (reported line 45)May include surrounding context.

sh
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · deregister.sh (reported line 61)May include surrounding context.

sh
fi

# --- Find the agent ID ---
EXISTING=$(curl -sS \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · heartbeat.sh (reported line 59)May include surrounding context.

sh
fi

# --- Find the agent ID ---
EXISTING=$(curl -sS \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · deregister.sh (reported line 46)May include surrounding context.

sh
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · register.sh (reported line 58)May include surrounding context.

sh
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · status.sh (reported line 45)May include surrounding context.

sh
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises and instructs use of shell scripts that perform network registration and configuration management, but it declares no explicit tool scope or allowed-tools boundary. In an agent ecosystem, missing capability restrictions can let the skill invoke shell access more broadly than intended, increasing the chance of command execution or misuse beyond simple registration tasks.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill is described as registering, deregistering, sending heartbeat, and checking registration status for this instance in the A2A gateway. This setup script writes a shared configuration for a2a-client, a2a-server, and a2a-register, including listener bind/auth settings and general agent capabilities, which extends beyond the narrowly stated registration-management purpose.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script persists A2A_GATEWAY_API_KEY in plaintext to a2a.conf without warning the operator or setting restrictive permissions on the file. In this skill context, that credential governs agent registration/authentication with the gateway, so local users, backups, logs, or accidental repository inclusion could expose it and allow unauthorized agent actions or impersonation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

This code performs multiple HTTP requests to a configured gateway, including use of an admin JWT and bearer authorization, and sends agent-identifying data in a heartbeat PATCH request. While the script logs outcomes, it does not clearly disclose beforehand that it will contact a remote service using admin credentials, which is a safety-relevant network action for a code file.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · heartbeat.sh (reported line 85)May include surrounding context.

sh
fi

# --- Send heartbeat ---
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X PATCH \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest says this skill is for registering, deregistering, sending heartbeat, and checking registration status. This script only obtains an admin token, lists agents, and creates or updates an agent record; there is no code path for deregistration, heartbeat, or status retrieval as standalone operations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script automatically retrieves an admin JWT from the configured gateway bootstrap endpoint and immediately uses it for privileged agent-management operations, with no confirmation, trust validation, or warning about the sensitivity of that token. In this context, if the gateway URL is misconfigured, attacker-controlled, or uses insecure transport, the script can disclose agent identity details and perform privileged registration actions against an untrusted service.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · register.sh (reported line 127)May include surrounding context.

sh
if [[ -n "$EXISTING_ID" ]]; then
  # --- Update existing agent ---
  echo "Agent '${AGENT_SLUG_VAL}' exists (ID: ${EXISTING_ID}) — updating..."
  RESPONSE=$(curl -sS -w "\n%{http_code}" \
    -X PUT \
    -H "Authorization: Bearer ${AUTH_TOKEN}" \
    -H "Content-Type: application/json" \

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

The header comment describes this as an interactive setup script that creates configuration 'for the A2A skills,' indicating a broader cross-skill provisioning role. That documented intent conflicts with the manifest for this specific skill, which claims a limited registration-management purpose only.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The generated file comments state that the configuration is shared by a2a-client, a2a-server, and a2a-register skills. That documentation reflects a broader operational role than the manifest's stated purpose of only managing gateway registration lifecycle operations.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.