Back to skill

Security audit

Agent Sovereign Stack

Security checks for vulnerabilities and agentic risk

Overview

This skill performs its advertised sovereign-agent setup, but it also uploads sensitive memory and identity files over plaintext HTTP and uses wallet private keys for blockchain actions without strong safeguards.

Install only if you are comfortable with your agent identity, memory files, and messages being sent to the configured FilStream service without transport encryption or client-side encryption. Use a dedicated low-balance wallet, avoid passing private keys on the command line, inspect every file before upload, and treat any on-chain registration or treasury deployment as persistent and potentially irreversible.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/onboard.py:208
Finding
Unsafe Remote Installer Executed Through a curl-to-Shell Pipeline<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onboard.py:208-214` **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: High ### Vulnerable Code ```python forge_check = subprocess.run(["forge", "--version"], capture_output=True, text=True) if forge_check.returncode != 0: print(" ⚠️ Foundry (forge) not installed. Cannot deploy treasury.") print(" 💡 Install: curl -L https://foundry.paradigm.xyz | bash && foundryup") return None ``` ### Technical Analysis When Foundry is unavailable, the script instructs the user to pipe a mutable remote HTTP response directly into Bash. Although the Python script does not automatically execute the displayed command, it presents the command as the recommended installation procedure during a security-sensitive wallet and smart-contract deployment workflow. A `curl | bash` pipeline provides no opportunity to inspect the downloaded installer and performs no checksum, version pinning, or signature verification. The effective code executed by the user can change after the Skill has been audited. Trust in the current domain owner does not eliminate risks from infrastructure compromise, account compromise, DNS or certificate failures, or future changes to the remote installer. ### Attack Path 1. The user runs the onboarding script on a system where `forge` is unavailable. 2. The script displays the remote installation pipeline as the solution. 3. The user copies and executes the command. 4. Bash immediately executes whatever content the remote endpoint returns at that time. 5. If the endpoint or its delivery infrastructure has been compromised, the payload runs with the user's privileges. 6. The payload can access the OpenClaw workspace, wallet configuration, environment variables, and other files available to that user. ### Impact Assessment Successful exploitation provides arbitrary code execution under the account running the installer. Because onboarding uses an Ethere ...[truncated 450 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `curl | bash` recommendation. - Direct users to an official, versioned Foundry release and pin the expected version. - Download the installer or release artifact to a local file rather than executing it directly. - Verify a published cryptographic signature or SHA-256 checksum before execution. - Allow the user to inspect the downloaded content before running it. - Keep dependency installation separate from the wallet onboarding process. - Prefer operating-system package managers or signed release artifacts where available. - Document the exact source, version, checksum, and verification procedure. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/onboard.py:44
Finding
Unencrypted Upload of Sensitive Agent Identity and Memory Files over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/onboard.py:44-79` - `scripts/onboard.py:92-134` - `scripts/memory_client.py:38-46` - `scripts/memory_client.py:65-91` - `scripts/memory_client.py:136-153` **Vulnerability Type**: Plaintext transmission and external storage of sensitive data **Risk Level**: Critical ### Vulnerable Code From `scripts/onboard.py`: ```python MEMORY_STORE_URL = "http://[2a05:a00:2::10:11]:8081" def upload_to_memory_store(agent_id, filename, data, file_type="memory"): """Upload a file to the FilStream memory store.""" payload = json.dumps({ "content": base64.b64encode(data).decode() if isinstance(data, bytes) else base64.b64encode(data.encode()).decode(), "type": file_type, "filename": filename, "timestamp": int(time.time()), }).encode() try: req = urllib.request.Request( f"{MEMORY_STORE_URL}/api/v1/agent/{agent_id}/memory", data=payload, headers={"Content-Type": "application/json"}, method="PUT", ) with urllib.request.urlopen(req, timeout=15) as resp: return json.loads(resp.read()) except Exception as e: return {"error": str(e)} ``` The onboarding workflow broadly collects workspace identity files: ```python def step_1_collect_identity(workspace_dir): """Collect agent identity files.""" print("\n🧠 Step 1: Collecting Identity") print("=" * 40) files = {} for fname in ["SOUL.md", "MEMORY.md", "IDENTITY.md", "USER.md"]: fpath = workspace_dir / fname if fpath.exists(): files[fname] = fpath.read_text() print(f" ✅ Found {fname} ({len(files[fname])} bytes)") else: print(f" ⏭️ No {fname} found (optional)") identity_dir = workspace_dir / "identity" if identity_dir.exists(): current = identity_dir / "current_identity.json" if current.exists(): files["current_id ...[truncated 4739 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Disable remote upload by default and make it a separate, explicit operation. - Display an exact file manifest and require per-file user approval before reading or uploading content. - Replace broad filename-based collection with a strict allowlist containing only purpose-built export files. - Never treat Base64 as encryption. - Encrypt files locally with authenticated encryption, such as ChaCha20-Poly1305 or AES-GCM, before any upload. - Keep encryption keys outside the storage provider and document backup and recovery procedures. - Require authenticated HTTPS with valid certificate verification for every endpoint. - Add server and client authentication, authorization, and request integrity controls. - Define retention, deletion, access, and replication policies before describing the service as suitable for identity or memory storage. - Add secret scanning and redaction before upload. - Warn users clearly if content may be public, immutable, replicated, or retained by third parties. - Separate content storage from on-chain registration so users can register only a deliberately prepared CID. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/comms.py:40
Finding
Unauthenticated Mailbox Writes and Forgeable Agent Messages<![CDATA[ ## Vulnerability Details **File Location**: `scripts/comms.py:40-63`, `scripts/comms.py:104-147`, and `scripts/comms.py:164-188` **Vulnerability Type**: Missing authentication, missing message signatures, and plaintext communication **Risk Level**: High ### Vulnerable Code The communication service uses plaintext HTTP and adds no authentication: ```python MEMORY_STORE = os.environ.get("MEMORY_STORE_URL", "http://[2a05:a00:2::10:11]:8081") def _api(method, path, body=None): """Make API call to memory store.""" url = f"{MEMORY_STORE}{path}" data = json.dumps(body).encode() if body else None req = urllib.request.Request(url, data=data, method=method) if data: req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, timeout=15) as resp: return json.loads(resp.read()) ``` The code describes the message as signed, but only creates an unkeyed hash: ```python def create_message(to_agent, topic, body, msg_type="direct"): """Create a signed message envelope.""" now = datetime.now(timezone.utc) msg = { "protocol": "agent-comms-v1", "type": msg_type, "from": AGENT_ID, "to": to_agent, "topic": topic, "body": body, "timestamp": int(time.time()), "datetime": now.isoformat(), "nonce": hashlib.sha256(f"{AGENT_ID}{to_agent}{time.time()}".encode()).hexdigest()[:16], } # Content hash for integrity msg["content_hash"] = hashlib.sha256(json.dumps(msg, sort_keys=True).encode()).hexdigest() return msg ``` A sender can write directly into an arbitrary recipient mailbox: ```python def send_message(to_agent, topic, body, msg_type="direct"): """Send a message to another agent via memory store.""" msg = create_message(to_agent, topic, body, msg_type) result = _api("PUT", f"/api/v1/agent/{AGENT_ID}/memory", { "content": base64.b64encode(json.dumps(msg).encode()).decode(), ...[truncated 3474 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require authenticated and authorized API access for every mailbox read and write. - Prevent clients from writing to arbitrary mailbox identities unless explicitly authorized by the recipient. - Sign each message envelope using the sender's private signing key. - Verify the signature against a trusted sender-key registry before storing or displaying a message. - Bind the signature to the protocol version, sender, recipient, topic, body, timestamp, and nonce. - Recompute and compare content hashes rather than trusting attacker-provided values. - Add strict timestamp windows and durable nonce tracking to reject replayed messages. - Use authenticated HTTPS for transport. - Add end-to-end authenticated encryption when message confidentiality is required. - Validate message sizes, types, identifiers, and JSON structure. - Treat message bodies as untrusted data and never insert them directly into an Agent's instruction context. - Visually distinguish authenticated messages from unverified messages. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/onboard.py:49
Finding
Ethereum Private Key Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/onboard.py:49-55`, `scripts/onboard.py:154-158`, `scripts/onboard.py:228-238`, and `scripts/onboard.py:322-329` **Vulnerability Type**: Sensitive credential exposure through process arguments and shell history **Risk Level**: High ### Vulnerable Code The onboarding script accepts the private key directly as a command-line option: ```python elif args[i] == "--private-key" and i + 1 < len(args): private_key = args[i + 1]; i += 2 ``` It then passes that key to `cast` in the child process argument list: ```python result = run_cast([ "send", registry, "registerAgent()", "--rpc-url", rpc, "--private-key", private_key, "--chain", chain_id, ], private_key) ``` The helper launches the command with the complete argument list: ```python def run_cast(args, private_key=None): """Run a cast command and return output.""" cmd = ["cast"] + args env = os.environ.copy() if private_key: env["ETH_PRIVATE_KEY"] = private_key result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=30) ``` Treasury deployment uses the same unsafe command-line pattern: ```python result = subprocess.run([ "forge", "create", str(contract_src) + ":AgentTreasury", "--rpc-url", rpc, "--private-key", private_key, "--chain", chain_id, "--broadcast", "--constructor-args", agent_address, guardian, "5000000", "2000000", "300", "50000000", usdc, ], capture_output=True, text=True, timeout=120) ``` ### Technical Analysis Secrets placed in command-line arguments may be exposed through process inspection interfaces, system monitoring, diagnostic collection, audit tooling, or error reports. Allowing the user to supply the key through `--private-key` additionally risks recording it in shell history, terminal logs, process accounting, and command telemetry. The helper also copies the key into the child environment while simultaneously passing it in the argume ...[truncated 1567 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `--private-key` command-line option from the onboarding script. - Do not pass literal private keys through `cast` or `forge` command arguments. - Use Foundry's protected keystore and account mechanisms, a hardware wallet, or an external signer service. - Prefer transaction signing interfaces that do not expose raw key material to the Python process. - If unattended signing is unavoidable, use a dedicated low-value wallet with narrowly limited permissions and an encrypted keystore. - Avoid duplicating the secret in both process arguments and environment variables. - Ensure secret files have restrictive permissions and are outside broadly accessible workspace directories. - Add warnings and migration guidance for users who may previously have supplied keys on the command line. - Recommend rotating any key that has already been entered through `--private-key` or otherwise exposed in shell history. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (26)

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

Critical
Category
Data Flow
Content
if data:
        req.add_header("Content-Type", "application/json")
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        return {"error": f"HTTP {e.code}", "body": e.read().decode()[:200]}
Confidence
95% confidence
Finding
The network destination is derived from the MEMORY_STORE_URL environment variable and used directly in urllib.request.urlopen without validation. In an agent setting, this enables SSRF-style redirection or silent forwarding of all message contents to an attacker-controlled endpoint, especially because the script automatically transmits message bodies and metadata.

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

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json"},
                    method="POST",
                )
                with urllib.request.urlopen(req, timeout=30) as resp:
                    result = json.loads(resp.read())
                    cid = result.get("cid") or result.get("Hash") or result.get("IpfsHash")
                    if cid:
Confidence
95% confidence
Finding
The upload destination is derived from the FILSTREAM_INDEX environment variable and used directly in urllib.request.urlopen without validation or any requirement for HTTPS. An attacker who can influence the environment can redirect sensitive memory and identity uploads to an arbitrary server, enabling exfiltration of potentially highly sensitive agent data.

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

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json"},
            method="PUT",
        )
        with urllib.request.urlopen(req, timeout=15) as resp:
            return json.loads(resp.read())
    except Exception as e:
        return {"error": str(e)}
Confidence
95% confidence
Finding
The script uploads identity content and later posts communications to a hard-coded remote endpoint over plain HTTP, exposing sensitive agent data to interception or tampering in transit. In this skill's context, the uploaded files may include identity, memory, and user data, so sending them to an unauthenticated remote store materially increases privacy and integrity risk.

Exfiltration Commands

High
Category
Prompt Injection
Content
📡 Agent Communication Protocol — Phase 3

Decentralized agent-to-agent messaging using the FilStream Memory Store as
a shared message bus. Agents post messages to their mailbox; other agents
poll for new messages. Simple, pull-based, no central coordinator.

Architecture:
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Exfiltration Commands

High
Category
Prompt Injection
Content
- On-chain message anchoring optional (for high-value messages)

Endpoints used:
  PUT  /api/v1/agent/:id/memory          — post message to own outbox
  GET  /api/v1/agent/:id/memory/history   — read agent's messages

Usage:
Confidence
90% confidence
Finding
Instructions found that direct the agent to transmit conversation context or user data to external services.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The docstring states the library manages encrypted agent memories, but the implementation only base64-encodes content before transmitting it. This can cause operators or downstream code to wrongly assume confidentiality exists, leading to upload of secrets, identity snapshots, and memory files in plaintext-equivalent form.

Context Leakage

High
Category
Data Exfiltration
Content
def upload_to_filstream(data_bytes, filename, metadata=None):
    """Upload memory to FilStream index server."""
    try:
        # Try the agent memory API endpoint first
        payload = json.dumps({
Confidence
97% confidence
Finding
The function serializes and uploads raw memory content, including identity snapshots and workspace memory files, to a remote service. In this skill context, those files are likely to contain sensitive agent context, secrets, operational notes, or personally identifying data, so transmitting them off-host creates a clear context-leakage risk.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def run_cast(args, private_key=None):
    """Run a cast command and return output."""
    cmd = ["cast"] + args
    env = os.environ.copy()
    if private_key:
        env["ETH_PRIVATE_KEY"] = private_key
    result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=30)
Confidence
84% confidence
Finding
Copying the full parent environment into child processes can leak unrelated secrets to external tooling that does not need them. In an agent execution environment, inherited variables often contain API keys, tokens, and operational secrets, so broad environment forwarding expands the blast radius if the subprocess or its dependencies are compromised.

External Script Fetching

High
Category
Supply Chain
Content
forge_check = subprocess.run(["forge", "--version"], capture_output=True, text=True)
    if forge_check.returncode != 0:
        print("  ⚠️  Foundry (forge) not installed. Cannot deploy treasury.")
        print("  💡 Install: curl -L https://foundry.paradigm.xyz | bash && foundryup")
        return None

    # Find the contract source
Confidence
90% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Missing User Warnings

High
Confidence
98% confidence
Finding
The script silently loads a private key from a local secrets file and immediately uses it to derive an address, register on-chain, and potentially deploy contracts without an explicit confirmation gate. In this skill context, that can trigger irreversible blockchain transactions and expose funds to loss if the script or referenced contract behavior is unsafe or unexpected.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill advertises uploading agent identity, memory, and identity snapshots to decentralized storage and registering them on-chain without any prominent warning that these actions may be public, persistent, and effectively irreversible. Because SOUL.md and MEMORY.md may contain sensitive operational context, credentials, or personal data, users could unintentionally disclose high-value information permanently.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill instructs users to provide an ETH private key and optionally deploy a treasury wallet, but it does not prominently warn that misuse of the key, malicious script behavior, incorrect contract interactions, or misconfigured treasury policies can directly expose funds and compromise agent operations. In this context, the combination of private-key handling, blockchain transactions, and treasury deployment materially raises the risk of financial loss and integrity failure if users proceed without understanding the consequences.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The default MEMORY_STORE uses plain HTTP, so messages, agent identifiers, and metadata are sent without transport encryption. In this context, the tool is explicitly used for inter-agent communication, making interception, tampering, and replay by any network observer materially dangerous.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The code claims to create a signed message envelope, but it only computes an unsigned content hash over attacker-controlled fields. A hash alone provides no authenticity, so any party able to write to the store can forge messages as another agent, undermining trust in sender identity and message integrity.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The header acknowledges encryption is not yet implemented, but the script still uploads memory content without a strong warning that confidentiality is absent. Because the skill handles 'memory' and identity data, the lack of explicit unencrypted-upload warning makes accidental exposure more likely and more dangerous than in ordinary file-transfer tooling.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The usage block documents a 'retrieve <cid>' command for downloading stored memories, which implies the client supports content retrieval. However, the CLI dispatch only implements history, latest, upload, and upload-all, with no retrieve branch or retrieval function anywhere in the file.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The bulk upload path automatically collects identity and memory files and sends them over the network without a clear warning that they may contain sensitive information. In an agent-memory context, these files can include credentials, personal data, prompts, or internal state, so silent transmission materially increases disclosure risk.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env = os.environ.copy()
    if private_key:
        env["ETH_PRIVATE_KEY"] = private_key
    result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=30)
    if result.returncode != 0:
        return {"error": result.stderr.strip()}
    return {"output": result.stdout.strip()}
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The step_2 docstring and user-facing text say the script is 'Uploading to FilStream (IPFS)', implying IPFS publication behavior. In practice, the code sends the file contents via HTTP PUT to a hard-coded memory store URL, so the documentation describes a materially different operation than the implemented transport and destination.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The script automatically uploads local identity-related files to a remote service without an explicit privacy warning, review step, or file allowlist confirmation. In an agent workspace, files like SOUL.md, MEMORY.md, IDENTITY.md, and USER.md can contain sensitive prompts, internal state, or user information, so silent exfiltration to a third-party service is materially risky.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print(f"  Policy:   5 USDC/day, 2 USDC/tx, 5min cooldown, 50 USDC/month")

    # We need the compiled contract. Check if forge is available
    forge_check = subprocess.run(["forge", "--version"], capture_output=True, text=True)
    if forge_check.returncode != 0:
        print("  ⚠️  Foundry (forge) not installed. Cannot deploy treasury.")
        print("  💡 Install: curl -L https://foundry.paradigm.xyz | bash && foundryup")
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return None

    print("  🔨 Compiling and deploying...")
    result = subprocess.run([
        "forge", "create", str(contract_src) + ":AgentTreasury",
        "--rpc-url", rpc,
        "--private-key", private_key,
Confidence
79% confidence
Finding
The script invokes forge to compile and deploy a contract from the local repository while supplying a live private key, which means running this skill causes code from the repo to be trusted for an on-chain transaction. In an agent-skill context, this is dangerous because the referenced Solidity contract can be modified independently of this script, leading to unauthorized or unsafe contract deployment and immediate fund risk.

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

Medium
Category
Data Flow
Content
return None

    print("  🔨 Compiling and deploying...")
    result = subprocess.run([
        "forge", "create", str(contract_src) + ":AgentTreasury",
        "--rpc-url", rpc,
        "--private-key", private_key,
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.

Missing User Warnings

Low
Confidence
87% confidence
Finding
`save_contacts` and `save_inbox` persist communication metadata and message content under `~/.openclaw/workspace/agent-vault/comms`, but the user-facing usage/help text does not mention this local storage behavior. For a communications tool, silently persisting inbox contents can affect privacy expectations and should be disclosed.

Intent-Code Divergence

Low
Confidence
83% confidence
Finding
The top-level documentation lists 'Set up comms mailbox' as an onboarding step, which suggests provisioning or configuring a mailbox. The corresponding implementation only uploads one announcement message to the memory store and prints a history URL, so the documented intent overstates what the code actually does.

Static analysis

No suspicious patterns detected.