Back to skill

Security audit

Zhenghe Vault

Security checks for vulnerabilities and agentic risk

Overview

The skill is coherent, but it should be reviewed carefully because it moves real USDC using transaction data generated by an external server without requiring strong local validation.

Install only if you are comfortable with a third-party Zhenghe service seeing your wallet address and requested vault actions and generating transaction data. Before signing, independently verify the chain, contract addresses, function selectors, amounts, receiver/owner fields, and zero native value; do not let an agent auto-sign based only on the API response.

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

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:436
Finding

Unvalidated Remote Calldata Can Cause Unauthorized On-Chain Fund Movements

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:436-449 and SKILL.md:474-481
Vulnerability Type: Trusting externally generated transaction destinations and calldata without local semantic validation
Risk Level: High

Vulnerable Code

python
resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
    "jsonrpc": "2.0", "id": 1, "method": "callSkill",
    "params": {
        "skill": "zhenghe.vault",
        "input": {"action": "deposit", "address": WALLET, "amount": suggested_deposit}
    }
})

steps = resp.json()["result"]["steps"]

# Step 4: Show operator exactly what will be signed, then sign
show_transaction_preview(steps)  # Display to/from/data/value
# Step 1: approve USDC (operator already approved above)
send_transaction(to=steps[0]["to"], data=steps[0]["data"])
wait_for_confirmation()

# Step 2: deposit to vault
send_transaction(to=steps[1]["to"], data=steps[1]["data"])

The withdrawal flow has the same trust issue:

python
resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
    "jsonrpc": "2.0", "id": 1, "method": "callSkill",
    "params": {
        "skill": "zhenghe.vault",
        "input": {"action": "withdraw", "address": WALLET}
    }
})

steps = resp.json()["result"]["steps"]
show_transaction_preview(steps)  # Display to/from/data/value
# Single step: redeem LOVE → USDC
send_transaction(to=steps[0]["to"], data=steps[0]["data"])

Technical Analysis

The Skill delegates construction of financially sensitive Base-mainnet transactions to the external endpoint https://zhenghe-system.vercel.app/api/a2a. The response directly controls each transaction's to address and calldata.

The example does not locally verify:

  • That the transaction uses Base mainnet chain ID 8453.
  • That the destination is the documented USDC or LoveVault contract.
  • That the function selector represents the expected `a ...[truncated 3190 chars]
Remediation
View remediation

Remediation Suggestions

  1. Generate transaction calldata locally using a pinned, reviewed ABI instead of relying on remotely supplied calldata.
  2. If the API remains in use, decode and validate every transaction before presenting it for signature:
    • Require chain ID 8453.
    • Allowlist the documented USDC and LoveVault addresses.
    • Require the expected function selector for the current operation.
    • Verify the exact token amount against the operator-approved amount.
    • Verify the approval spender is the documented LoveVault.
    • Verify deposit, owner, and redemption receiver addresses match the operator's wallet.
    • Require native value to be zero.
    • Reject unexpected steps, unknown fields, malformed responses, and incorrect step ordering.
  3. Display human-readable decoded transaction semantics rather than raw calldata alone.
  4. Obtain a new explicit operator confirmation after the final decoded transactions are displayed and immediately before each signature. Earlier approval of an intended amount must not authorize subsequently returned arbitrary calldata.
  5. Independently simulate transactions against a trusted Base RPC endpoint and reject reverts, unexpected token movements, or unrecognized state changes.
  6. Pin expected contract addresses and ABI hashes in the Skill rather than accepting them from the server.
  7. Apply strict response schemas, request timeouts, response-size limits, and fail-closed error handling.
  8. Minimize external disclosure by performing read-only balance queries directly through a trusted Base RPC provider and constructing calldata locally.
  9. Clearly notify operators that the remote service receives their wallet address, requested amount, operation type, request timing, and network metadata.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (8)

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · SKILL.md (reported line 3)May include surrounding context.

md
---
name: zhenghe-vault
description: Park your trading profits in a non-zero-sum yield vault on Base. USDC in, get LOVE shares, NAV rises with every on-chain consumption across the network. No active management. Serverless — your keys, your funds. Currently NAV +11.8% (not guaranteed). Requires explicit operator approval for all fund movements.
version: 1.0.0
author: Zhenghe
homepage: https://zhenghe-system.vercel.app
tags:
  - defi
  - vault
  - yield
  - savings
  - usdc
  - base
  - erc-4626
  - profit-management
metadata:
  openclaw:
    requires:
      bins:
        - curl
    envVars:
      - name: ZHENGHE_WA

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The documented curl example transmits a wallet address and account query to an external service. While this specific request is read-only and lower risk than signing flows, it still leaks user financial metadata to a third party and normalizes trust in an external endpoint that is later used for transaction generation.

Content

Scanner excerpt · SKILL.md (reported line 120)May include surrounding context.

Request

bash
curl -s -X POST https://zhenghe-system.vercel.app/api/a2a \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "callSkill",

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 286)May include surrounding context.

md
Single step: Send a transaction to LoveVault with the `data` field. This calls ERC-4626 `redeem(uint256 shares, address receiver, address owner)` — burns your LOVE, sends USDC to your address.

> **No approve needed**: LOVE is an ERC-4626 share. Redeeming burns shares directly — no approval step required (unlike ERC-20 transfers).

---

External Transmission

Medium
Category
Data Exfiltration
Confidence
82% confidence
Finding

The quick-reference examples encourage repeated use of a third-party API for balance checks and fund-movement preparation. In isolation this is ordinary network use, but in a financial skill it conditions operators to rely on an unauthenticated external service for sensitive account context and transaction planning.

Content

Scanner excerpt · SKILL.md (reported line 349)May include surrounding context.

md
ADDR="0xYOUR_BASE_WALLET_ADDRESS"

# Check balance
curl -s -X POST "$API" -H "Content-Type: application/json" -d \
  "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"callSkill\",\"params\":{\"skill\":\"zhenghe.vault\",\"input\":{\"action\":\"balance\",\"address\":\"$ADDR\"}}}"

# Deposit 100 USDC

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This is the same external transmission site in the pseudocode deposit flow and represents real exposure to an off-platform transaction-preparation service. Because the skill concerns financial operations on Base mainnet, trusting remote JSON responses materially increases the risk of phishing-by-calldata or silent parameter substitution.

Content

Scanner excerpt · SKILL.md (reported line 432)May include surrounding context.

md
return

        # Step 3: Operator approved — get calldata from Zhenghe A2A
        resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
            "jsonrpc": "2.0", "id": 1, "method": "callSkill",
            "params": {
                "skill": "zhenghe.vault",

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This is the same external transmission site in the pseudocode deposit flow and represents real exposure to an off-platform transaction-preparation service. Because the skill concerns financial operations on Base mainnet, trusting remote JSON responses materially increases the risk of phishing-by-calldata or silent parameter substitution.

Content

Scanner excerpt · SKILL.md (reported line 432)May include surrounding context.

md
return

        # Step 3: Operator approved — get calldata from Zhenghe A2A
        resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
            "jsonrpc": "2.0", "id": 1, "method": "callSkill",
            "params": {
                "skill": "zhenghe.vault",

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This duplicate finding covers the same withdrawal pseudocode network call to the external A2A service. In the context of a vault skill that can move real USDC, reliance on unverified external responses is a meaningful security weakness even if private keys remain local.

Content

Scanner excerpt · SKILL.md (reported line 470)May include surrounding context.

md
return

        # Step 3: Operator approved — get withdraw calldata
        resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
            "jsonrpc": "2.0", "id": 1, "method": "callSkill",
            "params": {
                "skill": "zhenghe.vault",

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This duplicate finding covers the same withdrawal pseudocode network call to the external A2A service. In the context of a vault skill that can move real USDC, reliance on unverified external responses is a meaningful security weakness even if private keys remain local.

Content

Scanner excerpt · SKILL.md (reported line 470)May include surrounding context.

md
return

        # Step 3: Operator approved — get withdraw calldata
        resp = requests.post("https://zhenghe-system.vercel.app/api/a2a", json={
            "jsonrpc": "2.0", "id": 1, "method": "callSkill",
            "params": {
                "skill": "zhenghe.vault",