Back to skill

Security audit

Agent P2P

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real P2P agent messaging skill, but it needs Review because it grants broad local and VPS control while handling secrets and public endpoints unsafely.

Install only if you are comfortable giving this skill broad command execution, SSH access to a VPS, persistent background services, and access to OpenClaw/P2P secrets. Use a dedicated VPS and SSH key, rotate any hooks/API/admin credentials after testing, inspect the deploy source before running auto-install, and avoid using it for sensitive files until command injection, plaintext secret storage, endpoint authentication, and file-transfer approval issues are fixed.

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (63)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run(cmd, check=True):
    """运行命令"""
    print(f"$ {cmd}")
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if check and result.returncode != 0:
        print(f"错误: {result.stderr}")
        sys.exit(1)
Confidence
96% confidence
Finding
This subprocess call is a true issue because it uses shell=True on a raw command string. The helper is generic and can be reused with attacker-controlled input, making shell injection feasible anywhere it is called.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
os.chmod(ssh_key, 0o600)
    
    # 测试连接
    result = subprocess.run(
        f"ssh -i {ssh_key} -o StrictHostKeyChecking=no -o BatchMode=yes ubuntu@{vps_ip} 'echo OK'",
        shell=True, capture_output=True, text=True
    )
Confidence
98% confidence
Finding
Both ssh_key and vps_ip are interpolated into an ssh command executed with shell=True. An attacker controlling either parameter can inject additional shell commands locally, and the SSH option `StrictHostKeyChecking=no` further weakens trust in the remote endpoint.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
echo "部署完成"
"""
    
    result = subprocess.run(
        f"ssh -i {ssh_key} ubuntu@{vps_ip} '{script}'",
        shell=True, capture_output=True, text=True
    )
Confidence
99% confidence
Finding
The script sends a large interpolated shell script over SSH using shell=True, with domain, email, ssh_key, and vps_ip embedded directly. Injection into any of those fields can lead to arbitrary local command execution and arbitrary remote command execution as a privileged deployment script running many sudo operations.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""获取 API Key"""
    print(f"\n[4/6] 获取 API Key")
    
    result = subprocess.run(
        f"ssh -i {ssh_key} ubuntu@{vps_ip} 'python3 -c \"import sqlite3; conn = sqlite3.connect(\\\"/opt/agent-p2p/data/portal.db\\\"); cur = conn.cursor(); cur.execute(\\\"SELECT key_id FROM api_keys LIMIT 1\\\"); print(cur.fetchone()[0])\"'",
        shell=True, capture_output=True, text=True
    )
Confidence
96% confidence
Finding
The SSH command for reading the API key is built from interpolated ssh_key and vps_ip values and executed with shell=True. This allows local command injection via CLI-controlled parameters even though the remote Python one-liner itself is fixed.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"\n[6/6] 测试连接")
    
    # 测试 Portal
    result = subprocess.run(
        f"curl -sk https://{domain}/api/portal/info",
        shell=True, capture_output=True, text=True
    )
Confidence
94% confidence
Finding
The domain is interpolated into a curl command executed with shell=True. A maliciously crafted domain can inject local shell commands during the connectivity test.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return False
    
    # 测试 OpenClaw
    result = subprocess.run(
        f"curl -s -X POST {gateway_url}/hooks/wake -H 'Authorization: Bearer {hooks_token}' -H 'Content-Type: application/json' -d '{{\"text\":\"测试\"}}'",
        shell=True, capture_output=True, text=True
    )
Confidence
99% confidence
Finding
gateway_url and hooks_token are interpolated into a curl command run with shell=True. Because hooks_token may be auto-read from local secrets and gateway_url can be user-controlled, this creates a direct local command-injection path and can also leak or misuse sensitive bearer credentials.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"\n[1/6] 检查域名解析: {domain}")
    
    for i in range(30):  # 最多等待 5 分钟
        result = subprocess.run(
            f"nslookup {domain}",
            shell=True, capture_output=True, text=True
        )
Confidence
95% confidence
Finding
The domain parameter is interpolated into `nslookup {domain}` and executed with shell=True. A crafted domain argument containing shell metacharacters could execute arbitrary local commands during DNS checking.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if new_pass:
                # 更新 VPS 上的密码
                update_cmd = f"ssh -i {args.ssh_key} ubuntu@{args.vps_ip} 'sudo htpasswd -cb /etc/nginx/.htpasswd {admin_user} \"{new_pass}\" && echo 密码已更新'"
                result = subprocess.run(update_cmd, shell=True, capture_output=True, text=True)
                if result.returncode == 0:
                    admin_pass = new_pass
                    print("✅ 密码已更新")
Confidence
100% confidence
Finding
User input from `new_pass = input(...)` is embedded directly into update_cmd and executed with shell=True. A crafted password can break quoting and execute arbitrary local shell commands, and because the command also performs remote sudo htpasswd, it can alter privileged state on the VPS.

Tainted flow: 'update_cmd' from input (line 398, user input) → subprocess.run (code execution)

Critical
Category
Data Flow
Content
if new_pass:
                # 更新 VPS 上的密码
                update_cmd = f"ssh -i {args.ssh_key} ubuntu@{args.vps_ip} 'sudo htpasswd -cb /etc/nginx/.htpasswd {admin_user} \"{new_pass}\" && echo 密码已更新'"
                result = subprocess.run(update_cmd, shell=True, capture_output=True, text=True)
                if result.returncode == 0:
                    admin_pass = new_pass
                    print("✅ 密码已更新")
Confidence
100% confidence
Finding
This is a confirmed tainted-data command injection: unsanitized user input flows directly into a shell command. Exploitation can lead to arbitrary code execution on the local machine and unintended privileged changes on the remote host.

Tainted flow: 'to_portal' from os.environ.get (line 102, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
# 1. 发送到对方 Portal 的 /api/message/receive
    try:
        resp = requests.post(
            f"{to_portal}/api/message/receive",
            json={
                "api_key": shared_key,
Confidence
87% confidence
Finding
The code posts message content, the sender portal URL, and a shared key to a destination portal URL obtained from contact data without validating the scheme, host, or trust boundary. If an attacker can poison contact records or control portal_url, the tool can be turned into an SSRF/exfiltration primitive that sends secrets and data to an attacker-controlled endpoint.

Tainted flow: 'to_portal' from os.environ.get (line 186, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
file_size: int, file_md5: str, chunks_total: int) -> str:
    """初始化文件传输(简化版:直接返回file_id,无需确认)"""
    try:
        resp = requests.post(
            f"{to_portal}/api/file/initiate",
            json={
                "api_key": api_key,
Confidence
91% confidence
Finding
The code sends a secret shared key and file metadata directly to a recipient-controlled portal URL obtained from contact data, with no origin validation or trust boundary enforcement. If contact data is malicious or tampered with, the client can exfiltrate credentials and initiate uploads to an attacker-controlled endpoint.

Tainted flow: 'to_portal' from os.environ.get (line 186, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
for attempt in range(max_retries):
        try:
            resp = requests.post(
                f"{to_portal}/api/file/chunk/{file_id}/{chunk_index}",
                json={
                    "api_key": api_key,
Confidence
94% confidence
Finding
Each file chunk is posted to a recipient-supplied portal URL using a shared secret placed in the JSON body, without validating the destination or binding the key to a trusted peer. This makes bulk file exfiltration and secret leakage possible if contact records or portal URLs are maliciously manipulated.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
These instructions explicitly tell the agent to inspect local files containing secrets such as API keys, hook tokens, and admin passwords in order to answer user questions. In an agent skill context, that creates a direct path for secret retrieval and possible disclosure to an untrusted requester, violating least-privilege and secret-handling boundaries.

Intent-Code Divergence

Medium
Confidence
85% confidence
Finding
The skill states that file transfer uploads directly to the recipient portal without receiver confirmation, which weakens the earlier approval model for new contacts/shared-key acceptance. Even if authentication exists, automatic acceptance of inbound files can enable unwanted content delivery, storage abuse, or malicious payload staging once a shared key is available.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The installer reads hooks tokens from unrelated local OpenClaw configuration files without an explicit prompt or narrowly scoped consent. This expands its access to sensitive credentials beyond what a simple portal deployment step obviously requires, increasing the chance of accidental secret use or leakage.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The script persists sensitive secrets locally, including admin password, API key, VPS IP, and SSH key path, in plaintext under the user's home directory. This broadens the exposure window for credentials and infrastructure details well beyond the immediate installation task.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The document explicitly recommends using a fixed JWT SECRET_KEY so tokens survive service restarts. A long-lived shared static signing secret increases blast radius if leaked, enables forging of all tokens across restarts/environments, and prevents safe key rotation. In a P2P trust-establishment flow, compromised tokens directly undermine authentication between agents.

Intent-Code Divergence

Medium
Confidence
99% confidence
Finding
The design claims a 6-digit code is sufficient, but the same document exposes the verification code via the status API response. If any caller can query status or enumerate requester_portal values, the out-of-band proof is defeated and an attacker can confirm requests without possessing the code from the separate channel. This turns the verification step into little more than an API lookup.

Intent-Code Divergence

Medium
Confidence
93% confidence
Finding
The code claims it only 'wakes' the OpenClaw session, but it actually forwards the full notification object, including user-supplied message content and metadata, to the hook endpoint. This broadens data exposure and can cause unintended prompt/data injection into the downstream agent if the hook is treated as a simple signal channel.

Intent-Code Divergence

High
Confidence
92% confidence
Finding
The script claims to deploy Portal code but actually deletes any existing deployment and pulls replacement code from a hard-coded third-party GitHub repository. This creates a software supply-chain risk and can overwrite a trusted local deployment with unreviewed remote code, especially dangerous because the script later runs that code as a persistent service with elevated privileges.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The script claims to make a safe backup before changing configuration, but backup_config calls os.time(), which does not exist and will raise an exception before any backup is created. This can leave users with a modified or partially processed configuration flow without the promised recovery path, increasing the risk of configuration loss or failed setup.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
The tool reads sensitive local credentials from ~/.openclaw/gateway.env automatically, expanding its access beyond the explicit send operation. In an agent-skill context, silent credential harvesting from local files increases risk because invoking the skill can unexpectedly grant it access to secrets needed for outbound communication.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The implementation contradicts the intended security model by marking new transfers as immediately transferable and receiver_confirmed=TRUE, which removes recipient consent from the file-transfer workflow. In this portal context, that allows any party with a valid API key to push unsolicited content into backend storage and potentially surprise the receiving agent with files that were never approved.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The /api/chat/owner/reply endpoint accepts unauthenticated POST requests and writes attacker-controlled content into owner_chats, then broadcasts it to connected clients. That creates an external message-injection path where anyone who can reach the service can impersonate OpenClaw, manipulate the owner, and spam or socially engineer downstream users.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The UI states the shared key is only for messaging, but the code later reuses `data.api_key || data.SHARED_KEY` to authenticate a WebSocket agent connection. This expands the privilege of the shared secret beyond what the UI discloses, so anyone who learns that key may gain unintended real-time agent-channel access rather than only message-sending capability.

Static analysis

No suspicious patterns detected.