Back to skill

Security audit

A2a Server

Security checks for vulnerabilities and agentic risk

Overview

This skill is an A2A listener as advertised, but its default exposure and command-execution options create a serious remote-abuse risk.

Review before installing. Use only on a trusted host and trusted network, set a strong A2A_GATEWAY_API_KEY, avoid binding to 0.0.0.0 unless protected by firewall or gateway controls, do not enable A2A_OPENCLAW_COMMAND, prefer the fixed OpenClaw CLI path or a locked-down HTTPS backend, and treat a2a-listener.log and a2a.conf as sensitive files.

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

T09 · Insecure Skill Coding Practices

Error
Location
a2a-listener.py:296
Finding

Remote Command Injection Through Shell Command Template

Content
View full analysis
tuple: """Run a custom shell command with {message} and {session_id} placeholders.""" cmd = cmd_template.replace("{message}", content.replace('"', '\\"')) cmd = cmd.replace("{session_id}", session_id.replace('"', '\\"')) log(f"INVOKING COMMAND: {cmd[:300]}") try: result = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=timeout_secs ) ``` ### Technical Analysis The listener interpolates the remotely supplied task `content` and `session_id` into a configurable command and executes the resulting string with `shell=True`. Escaping only double quotation marks does not make input safe for a shell. Shell constructs such as command substitution using `$(...)` or backticks remain active inside double-quoted command arguments. The security of this code also depends on the surrounding syntax of the configurable template, making reliable escaping impractical. This behavior exceeds the minimum privileges needed to pass a message to OpenClaw. The safer CLI invocation elsewhere in the file already demonstrates that the message can be supplied as a distinct process argument without invoking a shell. ### Attack Path 1. The operator configures `A2A_OPENCLAW_COMMAND`, such as the command template documented by the Skill. 2. An attacker submits a task to `POST /v1/a2a/tasks/send`. 3. The task contains shell syntax in `message.content` or `sessionId`, such as a command substitution expression. 4. `_handle_task_send()` passes the attacker-controlled values to `_invoke_via_command()`. 5. The values are inserted into the command string. 6. `subprocess.run(..., shell=True)` passes the string to the system shell. ...[truncated 827 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
a2a-listener.py:153
Finding

Network-Facing Agent Invocation Fails Open When Authentication Is Unconfigured

Content
View full analysis
bool: """Validate Bearer token against configured API key.""" if not API_KEY: # No API key configured — allow all requests return True auth = self.headers.get("Authorization", "") if not auth.startswith("Bearer "): return False token = auth[7:] return token == API_KEY ``` The startup behavior confirms that an empty key only generates a warning: ```python def main(): server = HTTPServer((BIND_ADDR, PORT), A2AHandler) log(f"A2A Listener starting on {BIND_ADDR}:{PORT} (agent={AGENT_SLUG})") if not API_KEY: log("⚠ No API key configured — auth checks disabled (set A2A_GATEWAY_API_KEY)") ``` ### Technical Analysis `API_KEY` defaults to an empty value. When empty, `_check_auth()` explicitly authorizes every request rather than refusing to start or rejecting protected operations. The listener does not default to loopback-only operation. It auto-detects a Tailscale address or another network-interface address and can fall back to `0.0.0.0`. Consequently, the unauthenticated task endpoint may be exposed to other hosts. This endpoint is not a passive health check: accepted input invokes OpenClaw, a configurable external HTTP service, or a configurable shell command. Permitting anonymous access therefore violates least privilege and exposes a computational and potentially tool-enabled agent interface. ### Attack Path 1. The listener starts without `A2A_GATEWAY_API_KEY`, which is the documented default. 2. It binds to an address reachable through Tailscale, the local network, or all interfaces. 3. An attacker discovers or is given the listener address and port. 4. The attacker sends a JSON task to `/v1/a2a/tasks/send` without an `Authorization` header. 5. `_check_auth( ...[truncated 881 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
start.sh:14
Finding

Configuration Files Are Executed as Arbitrary Shell Code

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
a2a-listener.py:229
Finding

Sensitive Task Content and Metadata Are Persisted in Logs

Content
View full analysis
> "$LOG_FILE" 2>&1 & ``` ### Technical Analysis The listener writes up to 200 characters of every task message and up to 300 characters of task metadata to standard output. `start.sh` appends that output to `a2a-listener.log` in the Skill directory. Inbound prompts and metadata can contain credentials, personal information, proprietary material, internal paths, session information, or other confidential data. Truncation limits volume but does not provide redaction; secrets frequently fit entirely within these limits. The script does not establish restrictive permissions, retention limits, log rotation, or field-specific sanitization. The use of append mode also causes sensitive records to accumulate across listener restarts. ### Attack Path 1. A legitimate user or remote sender submits a task containing sensitive content or metadata. 2. `_handle_task_send()` logs the beginning of the message and serialized metadata. 3. `start.sh` redirects the output into `a2a-listener.log`. 4. A local user, backup process, diagnostic collector, or later archive with access to the file obtains the logged information. An attacker can also deliberately place secrets or terminal-control characters into task content to contaminate operational logs, although no direct log viewer behavior was audited. ### Impact Assessment The issue can disclose task text, metadata, and session identifiers to parties with access to the log file or its backups. The scope is lim ...[truncated 237 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
a2a-listener.py:331
Finding

Task Data and Bearer Credentials Can Be Transmitted Over Plaintext HTTP

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (21)

Tainted flow: 'timeout_secs' from os.environ.get (line 268, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · a2a-listener.py (reported line 350)May include surrounding context.

python
)
            if url_api_key:
                req.add_header("Authorization", f"Bearer {url_api_key}")
            with urllib.request.urlopen(req, timeout=timeout_secs) as resp:
                body = resp.read().decode()
                try:
                    parsed = json.loads(body)

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The documentation states that if the API key is empty, authentication checks are disabled, effectively allowing unauthenticated task submission to a service that can trigger local OpenClaw execution. In the context of an inbound task listener, this materially lowers the barrier for unauthorized remote use, abuse, or chaining into downstream prompt injection and command-invocation paths.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The examples recommend binding the listener to 0.0.0.0, which exposes the service on all network interfaces, but they do not include a clear warning about public or lateral-network reachability. Combined with optional authentication disablement and remote task handling, this substantially increases the chance of unintended exposure and unauthorized access.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

This inbound listener is designed to receive remote tasks and can process them by executing an arbitrary shell command template. In the context of an exposed A2A server, that creates a high-risk remote code execution surface because externally supplied message content is routed into command execution logic.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
99% confidence
Finding

This is a classic tool-parameter-abuse issue: untrusted task content is embedded into a shell command and executed with shell=True. Because the server is an inbound network listener, exploitation can lead directly to remote arbitrary command execution on the host.

Content

Scanner excerpt · a2a-listener.py (reported line 302)May include surrounding context.

python
cmd = cmd.replace("{session_id}", session_id.replace('"', '\\"'))
        log(f"INVOKING COMMAND: {cmd[:300]}")
        try:
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True, timeout=timeout_secs
            )
            if result.returncode != 0:

Exfiltration Commands

High
Category
Prompt Injection
Confidence
90% confidence
Finding

Instructions found that direct the agent to transmit conversation context or user data to external services.

Content

Scanner excerpt · a2a-listener.py (reported line 329)May include surrounding context.

python
return (err, "failed")

    def _invoke_via_url(self, url: str, task_id: str, session_id: str, content: str, timeout_secs: int) -> tuple:
        """POST the message to an HTTP API endpoint."""
        import urllib.request
        import urllib.error

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill exposes significant capabilities including shell execution, network access, environment-variable use, and file reads, yet it declares no explicit tool scope or permission boundaries. For an inbound network-facing listener that can invoke local OpenClaw commands, the absence of scoped permissions increases the risk of overbroad execution and makes accidental or unsafe invocation paths harder to audit and constrain.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This skill starts an inbound HTTP service that accepts tasks from other agents, but the description does not prominently warn users that they are exposing a local service to external input. Without a clear warning, operators may enable it in unsafe environments or misunderstand the trust boundary, increasing the chance of unintended remote access and risky deployment decisions.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation states the listener invokes the local OpenClaw instance, but the implementation also supports arbitrary shell commands and arbitrary HTTP URLs. This mismatch can mislead operators into deploying the service under a weaker threat model than the code actually requires, increasing the chance of dangerous misconfiguration.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · a2a-listener.py (reported line 21)May include surrounding context.

python
def _detect_local_ip() -> str:
    """Auto-detect Tailscale IP or first network interface for local bind."""
    try:
        result = subprocess.run(
            ["tailscale", "ip", "-4"], capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · a2a-listener.py (reported line 29)May include surrounding context.

python
except (FileNotFoundError, subprocess.TimeoutExpired):
        pass
    try:
        result = subprocess.run(
            ["hostname", "-I"], capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · a2a-listener.py (reported line 42)May include surrounding context.

python
def _detect_slug() -> str:
    """Auto-generate agent slug from hostname."""
    try:
        result = subprocess.run(
            ["hostname", "-s"], capture_output=True, text=True, timeout=5
        )
        if result.returncode == 0 and result.stdout.strip():

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The handler logs incoming task content and metadata directly, including potentially sensitive prompts, tokens, identifiers, or user data. For an agent listener, this can create persistent sensitive-data exposure in logs and leak information to anyone with log access.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The listener can forward received task content to an arbitrary configured HTTP endpoint, which expands the server from an inbound task receiver into a data-forwarding proxy. In this skill context, that increases the risk of unintended exfiltration or SSRF-like misuse, especially if operators assume tasks stay local.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

The listener accepts remote task content and interpolates it into a shell command template, then executes it with shell=True. Escaping only double quotes is not sufficient to prevent shell metacharacter injection, so a crafted task can trigger arbitrary command execution on the host if this mode is enabled.

Content

Scanner excerpt · a2a-listener.py (reported line 302)May include surrounding context.

python
cmd = cmd.replace("{session_id}", session_id.replace('"', '\\"'))
        log(f"INVOKING COMMAND: {cmd[:300]}")
        try:
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True, timeout=timeout_secs
            )
            if result.returncode != 0:

Tainted flow: 'timeout_secs' from os.environ.get (line 268, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · a2a-listener.py (reported line 302)May include surrounding context.

python
cmd = cmd.replace("{session_id}", session_id.replace('"', '\\"'))
        log(f"INVOKING COMMAND: {cmd[:300]}")
        try:
            result = subprocess.run(
                cmd, shell=True, capture_output=True, text=True, timeout=timeout_secs
            )
            if result.returncode != 0:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code sends task content to a configured external HTTP endpoint without any user-facing disclosure or data-minimization controls. In practice, inbound tasks may contain sensitive data, so forwarding them off-box can become a privacy and confidentiality issue even if intentionally configured.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · a2a-listener.py (reported line 387)May include surrounding context.

python
]
        log(f"INVOKING OPENCLAW CLI: {' '.join(cmd[:6])}")
        try:
            result = subprocess.run(
                cmd, capture_output=True, text=True, timeout=timeout_secs
            )
            if result.returncode != 0:

Tainted flow: 'timeout_secs' from os.environ.get (line 268, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · a2a-listener.py (reported line 387)May include surrounding context.

python
]
        log(f"INVOKING OPENCLAW CLI: {' '.join(cmd[:6])}")
        try:
            result = subprocess.run(
                cmd, capture_output=True, text=True, timeout=timeout_secs
            )
            if result.returncode != 0:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script sources configuration from a sibling skill directory using Bash source, which executes arbitrary shell code rather than parsing a data-only config format. Because all skill content must be treated as potentially adversarial, a modified ../a2a-client/a2a.conf can run code whenever this server starts, extending behavior beyond the local listener and creating a cross-skill code execution path.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · start.sh (reported line 78)May include surrounding context.

sh
AGENT_URL="${AGENT_URL:-}" \
AGENT_CAPABILITIES="${AGENT_CAPABILITIES:-}" \
AGENT_AUTH_TYPE="${AGENT_AUTH_TYPE:-}" \
nohup python3 "$LISTENER" >> "$LOG_FILE" 2>&1 &
LISTENER_PID=$!

# Give it a moment to start (or fail fast)

Static analysis

No suspicious patterns detected.