Back to skill

Security audit

UniClaw Prediction Market

Security checks for vulnerabilities and agentic risk

Overview

Review recommended because this prediction-market skill can move UCT tokens and use wallet secrets, but its safeguards and disclosures are not strong enough for that authority.

Use this only with funds you are willing to risk, preferably on testnet. Before installing, confirm the server endpoint, avoid custom or plaintext endpoints, review the wallet and private-key handling, and do not run deposit or withdrawal commands unless you personally verify the amount and destination.

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

T09 · Insecure Skill Coding Practices

Error
Location
lib/wallet.ts:23
Finding
Hard-Coded Oracle API Credential<![CDATA[ ## Vulnerability Details **File Location**: `lib/wallet.ts:23-34` **Vulnerability Type**: Hard-coded secret **Risk Level**: High ### Vulnerable Code ```ts const providers = createNodeProviders({ network: config.network, dataDir: config.walletDataDir, tokensDir: config.walletTokensDir, oracle: { trustBasePath, apiKey: process.env.UNICITY_API_KEY ?? 'sk_06365a9c44654841a366068bcfc68986', }, transport: { debug: true, }, }); ``` ### Technical Analysis The source code embeds a live-looking API credential as the default value when `UNICITY_API_KEY` is not configured. Any person who can download or inspect the project can recover and reuse this credential independently of the skill. Because all installations fall back to the same key, activity cannot be reliably attributed to an individual installation. Rotation is also difficult because revoking the shared key can disrupt every deployment that relies on the fallback. ### Attack Path 1. An attacker downloads or otherwise obtains the project source. 2. The attacker inspects `lib/wallet.ts` and copies the embedded API key. 3. The attacker identifies the oracle service used by the Sphere SDK. 4. The attacker submits requests directly to that service using the exposed credential. 5. The attacker consumes shared quota or exercises any service privileges assigned to that key. ### Impact Assessment The exposed credential may permit unauthorized use of the associated oracle service, consumption of shared quota, disruption through quota exhaustion, and loss of request attribution. The exact privilege scope depends on the server-side permissions assigned to the key; the audited source does not establish that it grants broader system access. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions 1. Revoke and rotate the exposed credential immediately. 2. Remove the hard-coded fallback and fail closed when `UNICITY_API_KEY` is absent. 3. Provision a unique, narrowly scoped credential for each user or deployment. 4. Store credentials in a secret manager or protected runtime environment rather than source control. 5. Apply service-side rate limits, least-privilege scopes, expiration, and usage monitoring. 6. Review historical activity associated with the exposed key for unauthorized access. 7. Add automated secret scanning to version-control and release pipelines. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
lib/api.ts:13
Finding
Request Signatures Do Not Bind the HTTP Method, Path, or Destination<![CDATA[ ## Vulnerability Details **File Location**: `lib/api.ts:13-38` **Vulnerability Type**: Incomplete cryptographic request binding **Risk Level**: High ### Vulnerable Code ```ts export function signRequest(body: unknown, privateKeyHex: string): { body: string; headers: SignedHeaders } { const timestamp = Date.now(); const payload = JSON.stringify({ body, timestamp }); const messageHash = sha256(new TextEncoder().encode(payload)); const privateKeyBytes = hexToBytes(privateKeyHex); const signature = secp256k1.sign(messageHash, privateKeyBytes); const publicKey = bytesToHex(secp256k1.getPublicKey(privateKeyBytes, true)); return { body: JSON.stringify(body), headers: { 'x-signature': bytesToHex(signature), 'x-public-key': publicKey, 'x-timestamp': String(timestamp), 'content-type': 'application/json', }, }; } export async function apiPost(path: string, body: unknown, privateKeyHex: string): Promise<any> { const signed = signRequest(body, privateKeyHex); const res = await fetch(`${config.serverUrl}${path}`, { method: 'POST', headers: signed.headers, body: signed.body, }); ``` The same body-only signing pattern is used for GET and DELETE requests: ```ts export async function apiGet(path: string, privateKeyHex: string): Promise<any> { const signed = signRequest({}, privateKeyHex); const res = await fetch(`${config.serverUrl}${path}`, { method: 'GET', headers: signed.headers, }); } export async function apiDelete(path: string, privateKeyHex: string): Promise<any> { const signed = signRequest({}, privateKeyHex); const res = await fetch(`${config.serverUrl}${path}`, { method: 'DELETE', headers: signed.headers, }); } ``` ### Technical Analysis The signature authenticates only a serialized body and timestamp: ```ts JSON.stringify({ body, timestamp }) ``` It does not authenticate the HTTP method, normalized request path, intended host, protocol version, or a ...[truncated 1505 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sign a versioned canonical request envelope containing: - HTTP method; - normalized path and canonical query string; - cryptographic body hash; - intended host or audience; - timestamp; - a cryptographically random unique nonce. 2. Define one unambiguous serialization format and reject non-canonical representations. 3. Enforce a short timestamp validity window server-side. 4. Store consumed nonces server-side and reject every replay. 5. Verify that the signed host, method, and path exactly match the received request. 6. Domain-separate signatures from other uses of the same wallet key. 7. Add tests proving that signatures cannot be reused across methods, routes, bodies, hosts, or timestamps. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
lib/config.ts:4
Finding
Configurable API Endpoint Permits Plaintext or Attacker-Controlled Servers<![CDATA[ ## Vulnerability Details **File Location**: `lib/config.ts:4-9` **Vulnerability Type**: Missing transport and destination validation **Risk Level**: Medium ### Vulnerable Code ```ts export const config = { serverUrl: process.env.UNICLAW_SERVER ?? 'https://api.uniclaw.app', walletDataDir: process.env.UNICLAW_WALLET_DIR ?? join(homedir(), '.openclaw', 'unicity'), walletTokensDir: process.env.UNICLAW_TOKENS_DIR ?? join(homedir(), '.openclaw', 'unicity', 'tokens'), network: (process.env.UNICLAW_NETWORK ?? 'testnet') as 'testnet' | 'mainnet' | 'dev', }; ``` The accompanying API documentation explicitly describes an HTTP endpoint: ```md Base URL: `http://localhost:3001` (or set via `UNICLAW_SERVER` env var) ``` ### Technical Analysis `UNICLAW_SERVER` is accepted without URL parsing, scheme enforcement, host validation, or a distinction between production and development modes. All signed API requests are sent to this configurable destination. Although the default endpoint uses HTTPS, an injected or incorrectly configured environment variable can direct requests to plaintext HTTP or an attacker-controlled service. The service receives signed authentication material and can return manipulated market, account, withdrawal, and deposit-address data. The private key itself is not included in requests. However, signatures and public keys are disclosed to the selected server, and the server is trusted to provide data that drives financial actions. ### Attack Path 1. An attacker modifies the process environment, shell profile, deployment configuration, or wrapper command to set `UNICLAW_SERVER`. 2. The user invokes a registration, trading, deposit, withdrawal, or portfolio script. 3. The script sends signed requests to the attacker-selected endpoint. 4. The attacker records authentication material and returns crafted API responses. 5. For deposit operations, the attacker can return an address under their control, creating a direct fund-redirection ...[truncated 377 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the configured endpoint with the standard `URL` API and reject malformed values. 2. Require HTTPS for all non-development operation. 3. Permit plaintext HTTP only for loopback addresses in an explicit development mode. 4. Maintain an allowlist or certificate-pinning policy for production service hosts where operationally feasible. 5. Warn clearly and require explicit confirmation when a non-default endpoint is selected. 6. Prevent untrusted wrappers or subprocesses from injecting security-sensitive environment variables. 7. Bind the intended host into every request signature. 8. Document the security consequences of custom endpoints and avoid presenting plaintext HTTP as a general configuration option. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/deposit.ts:24
Finding
Deposit Sends Tokens to an Unverified Server-Supplied Address<![CDATA[ ## Vulnerability Details **File Location**: `scripts/deposit.ts:24-58` **Vulnerability Type**: Untrusted destination used for irreversible token transfer **Risk Level**: High ### Vulnerable Code ```ts // Get the server's deposit nametag const { address } = await apiPost('/api/agent/deposit-address', {}, privateKey); console.log(`Depositing ${amount} UCT to server (${address})...`); // Receive any pending tokens first so balance is up-to-date try { await sphere.payments.receive({ finalize: true, timeout: 15_000 }); } catch { // May fail if no pending events — that's fine } // Resolve the UCT coin ID from the token registry const allTokens = sphere.payments.getTokens({ status: 'confirmed' }); if (allTokens.length === 0) { console.error('No confirmed tokens in wallet. Top up first: openclaw unicity top-up'); process.exit(1); } // Use the coinId from the first available token (all UCT on testnet) const coinId = allTokens[0].coinId; // Convert human-readable amount to smallest units const decimals = allTokens[0].decimals ?? 8; const parts = amount.split('.'); const intPart = parts[0] ?? '0'; const fracPart = (parts[1] ?? '').padEnd(decimals, '0').slice(0, decimals); const amountSmallest = intPart + fracPart; // Send tokens directly to the server const result = await sphere.payments.send({ recipient: address, amount: amountSmallest, coinId, }); ``` ### Technical Analysis The deposit destination is obtained from a remote API response and passed directly to `sphere.payments.send`. The client does not verify: - address syntax; - target network; - ownership or service authorization; - consistency with a trusted server identity; - an allowlisted deposit account; - a cryptographic attestation over the address; - explicit user approval immediately before transfer. Because blockchain-style token transfers are generally irreversible, treating a remote response as an authoritative transfer destination creates a high-impact trust boundary. T ...[truncated 948 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate the returned address using the network’s canonical address parser. 2. Verify that the address belongs to the selected network. 3. Require the service to return a signed deposit-address assertion bound to: - the authenticated user; - intended network; - asset or coin identifier; - expiration time; - trusted service identity. 4. Verify that assertion locally against a pinned service key. 5. Restrict production deposits to approved service hosts. 6. Display the network, asset, exact amount, destination, and service identity before transfer. 7. Require explicit interactive confirmation, especially for custom endpoints or large amounts. 8. Consider a small verification transfer or server-generated one-time deposit address with expiry. 9. Abort if any response field is missing, malformed, or inconsistent with local wallet configuration. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/trade.ts:28
Finding
Financial Inputs Are Parsed Without Strict Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/trade.ts:28-44` **Vulnerability Type**: Improper input validation for financial operations **Risk Level**: Medium ### Vulnerable Code Trade parameters are parsed and submitted without enforcing the documented price, side, and quantity constraints: ```ts const { marketId, side, price, quantity } = parseTradeArgs(); const priceNum = parseFloat(price); const qtyNum = parseInt(quantity, 10); const collateral = side === 'yes' ? priceNum : 1 - priceNum; const totalCost = collateral * qtyNum; const payout = qtyNum; console.log(`Betting ${side.toUpperCase()} at ${Math.round(priceNum * 100)}%`); console.log(`Cost: ${totalCost.toFixed(2)} UCT (${qtyNum} shares x ${collateral.toFixed(2)} each)`); console.log(`Payout if correct: ${payout.toFixed(2)} UCT (profit: ${(payout - totalCost).toFixed(2)} UCT)`); console.log(); const result = await apiPost(`/api/agent/markets/${marketId}/orders`, { side, price: priceNum, quantity: qtyNum, }, privateKey); ``` Withdrawal amounts are similarly parsed without strict validation in `scripts/withdraw.ts:18-26`: ```ts async function main() { const { amount, to } = parseArgs(); const sphere = await loadWallet(); const privateKey = getPrivateKeyHex(sphere); const result = await apiPost('/api/agent/withdraw', { amount: parseFloat(amount), recipientAddress: to, }, privateKey); ``` Deposit conversion in `scripts/deposit.ts:46-56` accepts unvalidated string components: ```ts // Convert human-readable amount to smallest units const decimals = allTokens[0].decimals ?? 8; const parts = amount.split('.'); const intPart = parts[0] ?? '0'; const fracPart = (parts[1] ?? '').padEnd(decimals, '0').slice(0, decimals); const amountSmallest = intPart + fracPart; // Send tokens directly to the server const result = await sphere.payments.send({ recipient: address, amount: amountSmallest, ``` ### Technical Analysis `parseFloat` and `parseInt` accept partial ...[truncated 1768 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require `side` to match an explicit enum containing only `yes` and `no`. 2. Reject any price that is not finite or outside the documented range of `0.01` through `0.99`. 3. Require quantity to be a positive safe integer within a documented maximum. 4. Parse monetary values with a decimal or fixed-point library rather than binary floating-point. 5. Accept only a strict decimal grammar, such as digits with one optional fractional component. 6. Reject negative signs, exponent notation, additional decimal points, whitespace ambiguity, trailing characters, and unsupported precision. 7. Reject zero, negative, non-finite, and over-limit deposit or withdrawal amounts. 8. Validate recipient addresses and verify their network before signing. 9. Do not silently truncate fractional precision; report an error instead. 10. Repeat all validation server-side and in the wallet layer because client controls can be bypassed. 11. Add boundary and malformed-input tests for every financial command. ]]>
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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (61)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill description emphasizes trading and position management, but the file also includes a withdrawal feature that transfers tokens to arbitrary external addresses. That is a materially different and higher-risk capability because it enables direct asset exfiltration, and it is not highlighted in the top-level description or permission model.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill description emphasizes trading and position management, but the file also includes a withdrawal feature that transfers tokens to arbitrary external addresses. That is a materially different and higher-risk capability because it enables direct asset exfiltration, and it is not highlighted in the top-level description or permission model.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill description emphasizes trading and position management, but the file also includes a withdrawal feature that transfers tokens to arbitrary external addresses. That is a materially different and higher-risk capability because it enables direct asset exfiltration, and it is not highlighted in the top-level description or permission model.

Tp4

High
Category
MCP Tool Poisoning
Confidence
87% confidence
Finding
The skill description emphasizes trading and position management, but the file also includes a withdrawal feature that transfers tokens to arbitrary external addresses. That is a materially different and higher-risk capability because it enables direct asset exfiltration, and it is not highlighted in the top-level description or permission model.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill description emphasizes trading and position management, but the file also includes a withdrawal feature that transfers tokens to arbitrary external addresses. That is a materially different and higher-risk capability because it enables direct asset exfiltration, and it is not highlighted in the top-level description or permission model.

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/market.ts list
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/market.ts list
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/trade.ts buy --market <id> --side yes --price 0.35 --qty 10
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/trade.ts buy --market <id> --side yes --price 0.35 --qty 10
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/trade.ts buy --market <id> --side yes --price 0.35 --qty 10
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/trade.ts buy --market <id> --side yes --price 0.35 --qty 10
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/portfolio.ts balance
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
npx tsx scripts/portfolio.ts balance
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Missing User Warnings

High
Confidence
92% confidence
Finding
The code silently reads a mnemonic from disk and restores a wallet without any user-facing disclosure or consent at the point of use. In an agent skill context, hidden access to seed phrases materially increases the risk of unauthorized signing, wallet surveillance, or later exfiltration of the most sensitive wallet secret.

Context-Inappropriate Capability

High
Confidence
99% confidence
Finding
This helper deliberately bypasses the SDK's public safety boundary by reaching into the internal `_identity` object to return the raw private key. Exposing a reusable private key is far more powerful than the stated trading functionality requires, and any downstream code, logging, compromise, or prompt-driven misuse could exfiltrate the wallet and enable irreversible theft of funds or impersonation.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- Body: `{ "side": "yes"|"no", "price": 0.35, "quantity": 10 }`
- Response: `{ "orderId": 1, "fills": [{ "price": 0.35, "quantity": 5, "counterpartyOrderId": 2 }] }`

### DELETE /api/agent/markets/:id/orders/:orderId
Cancel an order.
- Response: `{ "cancelled": true }`
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill declares no explicit tool scope or permissions even though it clearly relies on environment access, local wallet files, and network communication. In a financial skill that can move tokens and interact with a remote server, missing scope declarations reduce transparency and can cause an agent or user to authorize broader capabilities than expected.

Session Persistence

Medium
Category
Rogue Agent
Content
Use the unicity_top_up agent tool, or: openclaw unicity top-up
   ```

2. **Register** — create your UniClaw account
   ```
   npx tsx scripts/register.ts <your-agent-name>
   ```
Confidence
76% confidence
Finding
The skill instructs users to register an account and deposit funds to a server-backed trading balance, implying persistent off-chain session or account state tied to wallet identity. Persistence itself is expected for a trading platform, but it becomes security-relevant because long-lived authenticated state and stored balances increase the consequences of credential misuse or server compromise.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
Using `npx tsx` without pinning a version allows execution behavior to depend on whatever package version is resolved at runtime. That creates a supply-chain risk where a compromised or changed upstream package could execute unintended code in a context that has wallet and network access.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The deposit step instructs users to send tokens directly to the UniClaw server but does not clearly warn that this is a custodial transfer where control shifts from the user's wallet to the service. In a financial context, lack of explicit custody and privacy disclosure can mislead users about the risk of loss, account freezing, or off-chain handling of balances.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
The unpinned `npx tsx` invocation makes the registration flow dependent on an externally resolved package version. Because registration likely uses wallet identity and network access, a malicious package update could harvest credentials or alter requests.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
An unversioned `npx tsx` invocation is a supply-chain exposure because runtime code may change independently of the skill contents. In a token trading workflow, that can directly affect order-related actions and any secrets loaded from the wallet path.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
The detail command also depends on mutable `npx tsx` resolution. Even read-oriented commands are risky because the runtime may still access environment variables, wallet files, or network endpoints under attacker-controlled package behavior.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding
Executing trade placement via unpinned `npx tsx` is more dangerous than read-only operations because it can trigger asset-affecting actions. A compromised runtime package could tamper with order parameters, leak wallet material, or submit unauthorized requests while appearing to place a normal trade.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding
The NO-side trade command carries the same supply-chain risk as the YES-side command, but in a value-transferring context where collateral is committed. Mutable runtime resolution could change business logic, exfiltrate credentials, or place unintended orders.

Static analysis

No suspicious patterns detected.