Skip to content

Potential settlement-ordering issue: paid resource can be released without completed settlement #2

Description

@chenshj73

Potential settlement-ordering issue: paid resource can be released without completed settlement

Hi, I noticed a possible payment-flow issue while reviewing the current repository state. This is a conservative report based on the visible code path, and I may be missing deployment-specific guards outside this repository.

Reviewed HEAD: 8869f1093cb2

What I observed

In the visible payment path, I observed:

x402 paid MCP gate returns paid tool output even when settlement fails: server awaits settleAfterExecution but ignores settled=false/error and returns result; wrapper path also ignores settlement outcome. Also SDK init failure disables payments and verify/settle fail open for requests carrying a header

Relevant code locations:

  • docs/X402_PAYMENTS.md
  • packages/mcp-server/src/server.ts
  • packages/mcp-server/src/payments/middleware.ts
  • packages/mcp-server/src/payments/service.ts
  • packages/mcp-server/package.json

Relevant code excerpts

docs/X402_PAYMENTS.md:160-184

 160   outputMint: '...',
 161   amount: 1000000,
 162 });
 163 ```
 164 
 165 ## Security Considerations
 166 
 167 1. **Payment Verification**: All payments are verified through the x402 facilitator before tool execution
 168 2. **Atomic Settlement**: Payments are settled only after successful tool execution
 169 3. **Rate Limiting**: Payment gating works alongside rate limiting
 170 4. **Audit Logging**: All paid tool calls are logged with payment details
 171 
 172 ## Testing
 173 
 174 For development, use Base Sepolia testnet:
 175 
 176 ```bash
 177 X402_NETWORK=base-sepolia
 178 X402_PAY_TO_ADDRESS=0x...  # Your testnet address
 179 ```
 180 
 181 Get testnet USDC from the [Base Faucet](https://faucet.base.org).
 182 
 183 ## Disabling Payments
 184 

packages/mcp-server/src/server.ts:158-182

 158           tool: name,
 159           params: args as Record<string, unknown>,
 160           result: 'success',
 161           duration,
 162         });
 163 
 164         // Settle payment after successful execution
 165         if (this.paymentMiddleware && paymentHeader) {
 166           await this.paymentMiddleware.settleAfterExecution(
 167             { toolName: name, arguments: args as Record<string, unknown>, headers: { 'x-payment': paymentHeader } },
 168             success
 169           );
 170         }
 171 
 172         return {
 173           content: [
 174             {
 175               type: 'text',
 176               text: JSON.stringify(result, null, 2),
 177             },
 178           ],
 179         };
 180       } catch (error) {
 181         const duration = Date.now() - startTime;
 182         const errorMessage = error instanceof Error ? error.message : 'Unknown error';

packages/mcp-server/src/payments/middleware.ts:136-160

 136     if (settlement.success) {
 137       logger.info({ 
 138         toolName, 
 139         transactionHash: settlement.transactionHash,
 140       }, 'Payment settled');
 141     } else {
 142       logger.error({ 
 143         toolName, 
 144         error: settlement.error,
 145       }, 'Payment settlement failed');
 146     }
 147 
 148     return {
 149       settled: settlement.success,
 150       transactionHash: settlement.transactionHash,
 151       error: settlement.error,
 152     };
 153   }
 154 
 155   /**
 156    * Get payment service instance
 157    */
 158   getService(): X402PaymentService {
 159     return this.service;
 160   }

packages/mcp-server/src/payments/service.ts:1-19

   1 /**
   2  * x402 Payment Service
   3  * Core payment verification and settlement using the x402 protocol
   4  */
   5 
   6 import type {
   7   X402Config,
   8   PaymentVerification,
   9   PaymentSettlement,
  10   PaymentSession,
  11   ToolPaymentRequirements,
  12   PaymentRequiredResponse,
  13   PaymentRequirementsList,
  14   PaymentEvent,
  15   PaymentEventHandler,
  16   PaymentNetwork,
  17 } from './types.js';
  18 import { getToolPrice, parsePrice } from './config.js';
  19 import { logger } from '../utils/logger.js';

packages/mcp-server/package.json:1-20

   1 {
   2   "name": "@boosty/mcp-server",
   3   "version": "1.0.0",
   4   "description": "Production DeFi operations MCP server for Solana with wallet management, trading, and volume campaigns",
   5   "main": "./dist/index.js",
   6   "module": "./dist/index.mjs",
   7   "types": "./dist/index.d.ts",
   8   "exports": {
   9     ".": {
  10       "types": "./dist/index.d.ts",
  11       "import": "./dist/index.mjs",
  12       "require": "./dist/index.js"
  13     }
  14   },
  15   "bin": {
  16     "defi-mcp-server": "./dist/cli.js"
  17   },
  18   "files": [
  19     "dist",
  20     "config"

Why this may matter

If the paid resource is returned after verification but before settlement is completed or enforced, a client may receive the protected result without a confirmed transfer.

Suggested check

Consider making the paid-resource path depend on server-trusted payment requirements and a completed payment state. In particular, re-check recipient/payTo, amount, asset, network, nonce/idempotency, resource binding, and settlement result at the exact point where the protected API/tool/content is released.

Conservative caveat

I only reviewed the code visible in this repository at the HEAD above. If deployment-specific middleware or an upstream service enforces the missing binding/settlement invariant, this may already be mitigated there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions