Skip to content

Latest commit

 

History

History
448 lines (346 loc) · 23.5 KB

File metadata and controls

448 lines (346 loc) · 23.5 KB

XPR Trustless Agents - Security Audit Report

Date: 2026-02-08 Auditor: 8-agent parallel audit swarm (Claude Opus 4.6) Scope: Full stack - 4 smart contracts, SDK, indexer, OpenClaw plugin, frontend, deployment/infra, cross-component consistency, test coverage


Executive Summary

8 specialized audit agents examined every source file across the entire XPR Agents stack. The audit identified 3 CRITICAL, 21 HIGH, 38 MEDIUM, and 40+ LOW severity findings. The most urgent issues are missing re-initialization guards on two contracts (allowing owner takeover), schema mismatches between the SDK/indexer and contracts (causing silent data corruption), and a missing indexer handler that permanently corrupts indexed state.

The codebase demonstrates strong fundamentals: parameterized SQL queries, proper auth checks on most actions, token transfer safety patterns (checks-effects-interactions), and no hardcoded secrets. The issues found are primarily gaps rather than flaws in existing logic.


Table of Contents

  1. Smart Contracts
  2. SDK
  3. Indexer
  4. OpenClaw Plugin
  5. Frontend
  6. Cross-Component Consistency
  7. Deployment & Infrastructure
  8. Test Coverage Gaps
  9. Recommended Fix Priority

1. Smart Contracts

CRITICAL

ID Contract Issue Location
SC-C01 agentfeed init() has no re-initialization guard - config can be overwritten by anyone with contract authority agentfeed.contract.ts:532-548
SC-C02 agentescrow init() has no re-initialization guard - same issue agentescrow.contract.ts:264-283
SC-C03 agentescrow setConfig() cannot update core_contract, feed_contract, acceptance_timeout, min_arbitrator_stake, or arb_unstake_delay - contract becomes unmanageable if core migrates agentescrow.contract.ts:285-311

SC-C01 / SC-C02 Details: Both agentcore and agentvalid correctly check existingConfig.owner == EMPTY_NAME before allowing initialization. The agentfeed and agentescrow contracts skip this check, meaning init() can be called again to overwrite the owner field.

Fix: Add const existingConfig = this.configSingleton.get(); check(existingConfig.owner == EMPTY_NAME, "Contract already initialized."); to both contracts.

HIGH

ID Contract Issue Location
SC-H01 agentfeed FeedbackRateLimit secondary index uses XOR (reviewer.N ^ agent.N) - guaranteed collisions can cause CPU exhaustion DoS agentfeed.contract.ts:329-331
SC-H02 agentfeed/agentvalid/agentescrow Permissionless cleanup actions can delete live data, desync scores when followed by recalculate() agentfeed.contract.ts:1107-1128, agentvalid.contract.ts:994-1015, agentescrow.contract.ts:1058-1088
SC-H03 agentescrow No dispute timeout mechanism FIXED - resolvetmout action allows owner to resolve disputes after 14 days agentescrow.contract.ts:830+
SC-H04 agentfeed calcaggtrust overwrites native avg_score in agentscores table - next submit() overwrites it back, causing score oscillation agentfeed.contract.ts:1517-1573

MEDIUM

ID Contract Issue Location
SC-M01 agentfeed init() does not validate core_contract is a real account agentfeed.contract.ts:532-548
SC-M02 agentfeed submit() does not call updateDirectionalTrust() but submitctx() and submitwpay() do agentfeed.contract.ts:630-697
SC-M03 agentescrow No validation that symbol parameter matches XPR in createJob() agentescrow.contract.ts:324-388
SC-M04 agentescrow Most actions missing pause check (createjob, addmilestone, acceptjob, startjob, submitmile, deliver, dispute) Multiple locations
SC-M05 agentescrow Arbitrator can deactivate while assigned to active disputes, triggering owner fallback (0% fee dodge) agentescrow.contract.ts:937-944
SC-M06 agentcore getSystemStake() integer truncation at boundary (9999 / 10000 = 0) agentcore.contract.ts:289-301
SC-M07 agentfeed amount_paid is self-reported and unverified in submit() agentfeed.contract.ts:630-697
SC-M08 agentcore hashString() (DJB2) collisions for plugin name secondary index agentcore.contract.ts:144-150

LOW

ID Contract Issue
SC-L01 agentvalid cleanValidations can delete validations with pending unfunded challenges
SC-L02 agentfeed cleanFeedback deletes records without updating agentscores
SC-L03 agentescrow Milestone order not validated for uniqueness
SC-L04 agentescrow No maximum deadline enforcement on createJob()
SC-L05 agentvalid resolve() resets challenged = false, enabling cumulative slashing
SC-L06 agentescrow arb_unstake_delay not configurable via init() FIXED - positional arg now passes 604800 (7 days)

INFO

ID Contract Issue
SC-I01 All Rounding dust from integer division in fee calculations
SC-I02 agentcore transfer action name collision with token notify handler
SC-I03 All Singleton config defaults create valid-looking config with owner == EMPTY_NAME
SC-I04 agentfeed Context embedded in tags field causes false colon detection
SC-I05 agentvalid Accuracy dilutable by volume padding

2. SDK

HIGH

ID Issue Location
SDK-H01 ValidatorRaw missing pending_challenges field - positional data corruption for registered_at and active types.ts:191-213
SDK-H02 Challenge field order mismatch / missing funded_at - every field after stake reads wrong value ValidationRegistry.ts:155-176
SDK-H03 Arbitrator missing active_disputes field - active filter permanently broken EscrowRegistry.ts:317-343
SDK-H04 No validation on amount string in registerWithFee, claimWithFee, and fee methods Multiple locations

MEDIUM

ID Issue Location
SDK-M01 listFeedbackForAgent secondary index queries fetch globally, not scoped to account FeedbackRegistry.ts:52-105
SDK-M02 No validation on score (1-5 range) before transaction FeedbackRegistry.ts:189-215
SDK-M03 No validation on confidence (0-100 range) before transaction ValidationRegistry.ts:307-332
SDK-M04 parseInt() without radix or NaN guard throughout all parse methods Multiple files
SDK-M05 Trust score longevity uses client Date.now() instead of chain time utils.ts:50-52
SDK-M06 safeJsonParse<T> does type assertion not runtime validation (prototype pollution risk) utils.ts:129-135
SDK-M07 Account names never validated on write operations despite isValidAccountName() existing Multiple write methods
SDK-M08 parseXpr uses floating-point math, losing precision (0.7 * 10000 = 6999.999...) utils.ts:171-175

LOW

ID Issue
SDK-L01 DISPUTE_RESOLUTIONS array index access without bounds check
SDK-L02 console.warn in transferOwnership leaks implementation details
SDK-L03 listPlugins hardcoded limit of 1000 with no pagination
SDK-L04 Cleanup methods have no maxAge/maxDelete validation
SDK-L05 MilestoneRaw.order typed as number inconsistent with other raw fields
SDK-L06 @proton/js caret version range allows untested minor versions

3. Indexer

HIGH

ID Issue Location
IDX-H01 No SSRF protection on webhook URLs FIXED - isValidWebhookUrl() validates at registration (blocks private IPs, localhost, metadata endpoints, non-HTTPS schemes) routes.ts:395-433
IDX-H02 SQL sort column interpolation pattern - safe by accident, fragile to future changes routes.ts:28-29

MEDIUM

ID Issue Location
IDX-M01 Webhook admin token comparison uses !== (timing attack susceptible) routes.ts:366-367
IDX-M02 Unbounded webhook_deliveries table growth (no cleanup) dispatcher.ts:173-186
IDX-M03 Unbounded events table growth (no TTL or rotation) schema.ts:136-144
IDX-M04 No rate limiting on any endpoint index.ts
IDX-M05 Synthetic ID drift (MAX(id) + 1) on missed blocks All handlers

LOW

ID Issue
IDX-L01 CORS fully open (Access-Control-Allow-Origin: *)
IDX-L02 Unauthenticated POST /admin/sync-kyc FIXED - requireAdminAuth guard added
IDX-L03 Webhook tokens stored in plaintext in SQLite
IDX-L04 No validation of Hyperion stream data schema
IDX-L05 Docker container runs as root
IDX-L06 No WebSocket origin/auth validation on reconnect
IDX-L07 Error handler logs full error objects (may leak internals)
IDX-L08 Unbounded concurrent webhook deliveries (no concurrency limit)

Positive Findings

  • All SQL queries properly parameterized
  • Webhook auto-disable after 50 failures
  • Query result limits enforced (500 max)
  • 10-second webhook delivery timeout with AbortSignal
  • 4xx errors not retried
  • Graceful shutdown (SIGINT/SIGTERM)
  • Irreversible-only stream processing
  • Exponential backoff on reconnection

4. OpenClaw Plugin

HIGH

ID Issue Location
OC-H01 Contract names from config not validated - config compromise redirects all financial operations index.ts:38-51
OC-H02 indexerUrl not validated - config compromise enables SSRF and data exfiltration indexer.ts:10-17

MEDIUM

ID Issue Location
OC-M01 3 agent write tools missing confirmation gate (update_agent, set_status, manage_plugin) agent.ts:191-292
OC-M02 dispute_feedback and recalculate_score missing confirmation gate feedback.ts:161-201
OC-M03 register_validator, submit_validation, challenge_validation missing confirmation gate validation.ts:140-232
OC-M04 Default maxTransferAmount is 10,000 XPR - very generous for autonomous agent index.ts:53
OC-M05 validateUrl function exists but is never called on any URI/endpoint field agent.ts, feedback.ts, validation.ts, escrow.ts
OC-M06 No session null guard on 18+ write tools - cryptic errors in read-only mode All write tool files
OC-M07 Docker ports bound to 0.0.0.0 FIXED - Bound to 127.0.0.1 docker-compose.yml:16,39

LOW

ID Issue
OC-L01 accept_job, deliver_job, submit_milestone missing confirmation gate
OC-L02 Falsy fee_amount (0) skips validation guard
OC-L03 Floating-point precision in amount conversion FIXED - xprToSmallestUnits() uses string-split integer math
OC-L04 No string length limits on any field
OC-L05 Account name regex allows leading/trailing dots
OC-L06 Private key in memory with no zeroization (inherent JS limitation)
OC-L07 Internal URL leaked in health check error response
OC-L08 Raw error messages propagated to tool output
OC-L09 Private key passed as Docker environment variable (visible via docker inspect)
OC-L10 setup.sh appends tokens without dedup on re-run

5. Frontend

MEDIUM

ID Issue Location
FE-M01 Unprotected JSON.parse on chain-sourced capabilities field - crashes agent list registry.ts:84,112
FE-M02 Missing security headers (CSP, X-Frame-Options, HSTS, etc.) next.config.js
FE-M03 Agent endpoint URL rendered without protocol validation - future XSS risk if made clickable [id].tsx:119-121
FE-M04 Agent description injected into <meta> tag without sanitization [id].tsx:43

LOW

ID Issue
FE-L01 Raw error messages from RPC surfaced to UI
FE-L02 No client-side score range validation (1-5)
FE-L03 Tag field allows comma injection
FE-L04 Wallet session not shared via React Context (stale state across components)
FE-L05 Staking amount parsed as float without NaN/negative checks
FE-L06 No rate limiting on form submissions
FE-L07 Inconsistent rel="noopener noreferrer" on external links

Positive Findings

  • Zero dangerouslySetInnerHTML usage
  • No private key handling in frontend code
  • No localStorage/sessionStorage for sensitive data
  • No eval or Function constructors
  • No API routes (all data from RPC)
  • CSRF inherently mitigated by wallet signing
  • Self-review prevention in FeedbackForm
  • Environment variables use NEXT_PUBLIC_ prefix correctly

6. Cross-Component Consistency

Schema Mismatches (HIGH)

Entity Missing Field SDK Impact Indexer Impact
Validator pending_challenges Fields after accuracy_score read wrong values Column missing, can't track
Challenge funded_at Missing critical timestamp for dispute period Column missing
Arbitrator active_disputes active filter permanently broken Column missing

Missing Indexer Handlers (HIGH) - ALL FIXED

Action Contract Impact
expirefunded agentvalid Funded challenge expiry permanently corrupts indexer FIXED - Handler exists at validation.ts:321-349
resolvetmout agentescrow Timeout resolution not indexed FIXED - Handler added to escrow.ts

Missing Indexer Handlers (LOW - cleanup actions)

Actions Impact
cleanjobs, cleandisps (agentescrow) Indexer retains records chain has pruned
cleanvals, cleanchals (agentvalid) Same
cleanfback, cleandisps (agentfeed) Same

Other Mismatches

Severity Issue
MEDIUM SDK trust score hardcodes 'agentfeed' instead of reading from config
MEDIUM OpenClaw protocol description suggests invalid values (a2a, mcp, rest) - contract requires http, https, grpc, websocket, mqtt, wss
MEDIUM Indexer feedback reviewer_kyc_level always 0 (contract reads it internally, not in action data)
MEDIUM Indexer lacks time-based score decay that contract applies
LOW Indexer agents.stake column never populated (agents use system staking)
LOW Indexer agents.trust_score column always 0 (never computed)
LOW Milestone column naming: contract order vs indexer milestone_order

7. Deployment & Infrastructure

HIGH

ID Issue Location
INF-H01 Wildcard CORS on indexer (Access-Control-Allow-Origin: *) index.ts:36
INF-H02 Unauthenticated POST /admin/sync-kyc FIXED - requireAdminAuth guard added routes.ts:365-366
INF-H03 No rate limiting on any API endpoint index.ts

MEDIUM

ID Issue Location
INF-M01 Deploy script has no mainnet guard / chain verification deploy-testnet.sh:13
INF-M02 Docker ports exposed to all interfaces FIXED - Bound to 127.0.0.1 in both docker-compose files docker-compose.yml
INF-M03 Docker container runs as root indexer/Dockerfile
INF-M04 setup.sh token duplication on re-run setup.sh:52-61
INF-M05 Webhook tokens stored in plaintext in SQLite schema.ts:286
INF-M06 No backup/recovery mechanism for indexer database docker-compose.yml:46

LOW

ID Issue
INF-L01 OpenClaw gateway image not pinned (latest tag)
INF-L02 All dependencies use caret ranges + package-lock.json gitignored
INF-L03 next-env.d.ts not in .gitignore

Positive Findings

  • No hardcoded secrets anywhere in codebase
  • .env files properly gitignored
  • Cryptographically secure token generation (openssl rand -hex 32)
  • Environment variable validation in setup.sh
  • Multi-stage Docker build
  • set -e / set -euo pipefail in shell scripts
  • Sensitive database files gitignored

8. Test Coverage Gaps

Estimated Coverage by Component

Component Tests Coverage Status
agentcore contract 67 ~80% onTransfer, ownership, claim deposits tested
agentfeed contract 44 ~70% Recalculation, rate limiting, score calculation, cleanup tested
agentvalid contract 37 ~75% Challenge resolution, slashing, accuracy tracking tested
agentescrow contract 45 ~75% Timeouts, milestones, arbitrator-less fallback tested
SDK 183 ~95% safeParseInt, parseXpr edge cases added
OpenClaw plugin 52 ~65% maxTransferAmount enforcement, confirmation gate tested
Indexer 28 ~60% Handler tests for all 4 contracts, schema, transfers, event logging
Frontend 0 0% Zero test coverage
Integration (test-actions.sh) ~70 ~70% Missing: timeouts, context feedback, funded challenge timeout
Total 456

Resolved Test Gaps (Phase 4 - completed 2026-02-08)

All CRITICAL and HIGH test gaps from the original audit have been resolved:

  1. Indexer: 0% coverage - 28 handler tests added (schema, agent, feedback, validation, escrow, transfers, events)
  2. Contract onTransfer handlers - 15 agentcore onTransfer tests (claim deposits, malformed memos, excess refunds)
  3. Challenge resolution + slashing - 9 agentvalid tests (slash on upheld, stake forfeiture, accuracy tracking, dispute period)
  4. Job timeout / acceptance timeout - 7 agentescrow tests (acceptance timeout, deadline timeout, milestone approval)
  5. Paginated recalculation - 10 agentfeed tests (single/multi-batch, offset validation, cancellation, expiry, blocking)
  6. KYC-weighted scoring - 5 agentfeed tests (avg_score calculation, perfect/minimum scores, dispute subtraction)
  7. OpenClaw maxTransferAmount enforcement - 6 OpenClaw tests (register, feedback, stake, create job, fund job, within-limit)

Remaining Gaps

  1. Frontend: 0% coverage - React components untested
  2. Integration test-actions.sh - Missing timeout and context feedback paths
  3. Indexer API routes - REST endpoint response format untested
  4. Indexer webhook dispatcher - Retry logic and auto-disable untested

9. Recommended Fix Priority

Phase 1 - Blockers (fix before any testnet deployment) - ALL DONE

# Fix Effort Status
1 Add re-init guards to agentfeed.init() and agentescrow.init() 2 lines Done
2 Fix SDK schema: add pending_challenges, funded_at, active_disputes ~30 lines Done
3 Add expirefunded handler to indexer ~20 lines Done (already existed)
4 Fix OpenClaw protocol description 1 line Done

Phase 2 - Security hardening (fix before public testnet) - ALL DONE

# Fix Effort Status
5 Add CORS allowlist to indexer 5 lines Done
6 Add auth to /admin/sync-kyc or remove it 3 lines Done - requireAdminAuth guard
7 Add SSRF protection for webhook URLs 20 lines Done - isValidWebhookUrl()
8 Bind Docker ports to 127.0.0.1 2 lines Done - both compose files
9 Add missing params to agentescrow.setConfig() 15 lines Done - setconfig updated
10 Add dispute timeout mechanism to agentescrow 50 lines Done - resolvetmout action
11 Add missing indexer columns (pending_challenges, active_disputes, funded_at) 10 lines Done
12 Add session null guard to OpenClaw write tools 20 lines Done
13 Wire validateUrl to URI/endpoint fields 10 lines Done

Phase 3 - Before mainnet

# Fix Effort Status
14 Add rate limiting to indexer API (express-rate-limit) 15 lines Done
15 Add security headers to frontend (CSP, X-Frame-Options) 20 lines
16 Run Docker as non-root user 5 lines Done
17 Add JSON.parse try-catch in frontend registry.ts 10 lines
18 Fix parseXpr floating-point precision 10 lines Done - xprToSmallestUnits() integer math
19 Add parseInt NaN guards throughout SDK 30 lines Done - safeParseInt()
20 Pin Docker images and commit lockfiles 5 lines
21 Add pause checks to remaining agentescrow actions 10 lines
22 Block arbitrator deactivation with active disputes 2 lines
23 Lower default maxTransferAmount to 100 XPR 1 line

Phase 4 - Test coverage (COMPLETED 2026-02-08)

# Area Priority Status
24 Indexer test suite (handlers, API, webhooks) CRITICAL Done (28 tests)
25 Contract onTransfer handler tests CRITICAL Done (15 tests)
26 Challenge resolution + slashing tests CRITICAL Done (9 tests)
27 Job timeout / acceptance timeout tests CRITICAL Done (7 tests)
28 Paginated recalculation tests HIGH Done (10 tests)
29 KYC-weighted scoring tests HIGH Done (5 tests)
30 OpenClaw maxTransferAmount enforcement tests HIGH Done (6 tests)

Phase 5 - E2E audit swarm fixes (COMPLETED 2026-02-08)

# Fix Severity Status
31 Add platform_fee <= 1000 upper bound to agentescrow.init() CRITICAL Done
32 Fix agentescrow.init() positional arg coercion (false0 for arb_unstake_delay) HIGH Done
33 Add resolvetmout indexer handler (job state 8, dispute resolution, active_disputes decrement) HIGH Done
34 Add resolveTimeout() SDK method on EscrowRegistry HIGH Done
35 Add xpr_resolve_timeout OpenClaw tool with confirmation gate HIGH Done
36 Add confirmation gate to xpr_accept_job HIGH Done
37 Fix floating-point amount conversion across all indexer handlers (integer math) MEDIUM Done
38 Fix floating-point amount conversion in all OpenClaw tools (xprToSmallestUnits()) MEDIUM Done
39 Fix job state 3 description: ACTIVEINPROGRESS in OpenClaw MEDIUM Done
40 Fix xpr_dispute_feedback description (reviewer can also dispute) MEDIUM Done
41 Fix validator stake memo matching (stake or stake:* prefix) MEDIUM Done
42 Update OpenClaw test counts (43→44 tools, 13→14 escrow) TEST Done

Methodology

This audit was conducted by 8 specialized agents running in parallel:

  1. Smart Contract Agent - Read all 4 contracts line-by-line checking for auth flaws, reentrancy, integer overflow, state machine violations, economic exploits
  2. SDK Agent - Audited type safety, cross-contract schema consistency, input validation, key exposure
  3. Indexer Agent - Checked for SQL injection, webhook SSRF, DoS, data integrity, WebSocket security
  4. OpenClaw Plugin Agent - Audited confirmation gates, amount limits, input validation, starter kit security
  5. Frontend Agent - Checked for XSS, CSRF, SSR issues, wallet state management, URL rendering
  6. Cross-Component Agent - Verified schema consistency, state machine agreement, fee handling, memo parsing across all 6 components
  7. Test Coverage Agent - Analyzed every test file against source code to find gaps
  8. Deployment/Infra Agent - Audited scripts, Docker configs, secret handling, dependency supply chain

Each agent read every relevant source file in its domain and cross-referenced findings against the contract source of truth.