Back to skill

Security audit

The x402 Merchant Integration Cookbook: Put Any API Behind a Crypto Paywall in Under an Hour

Security checks for vulnerabilities and agentic risk

Overview

This is a non-executable payment-integration guide, but some payment-control examples are unsafe to copy into real services without review.

Install only if you will treat it as educational material, not production-ready payment code. Before using the examples with real money or protected content, align the environment variable names, remove unnecessary secrets, validate facilitator responses strictly, fail closed on verification problems, keep bundle balances server-side, and avoid putting real private keys in copied test code.

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

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:297
Finding

Payment Verification Trusts HTTP Status Instead of Explicit Facilitator Validation

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:1516
Finding

Client-Held Bundle Counter Allows Replay and Unlimited Paid Access

Content
View full analysis
str: """Issue a JWT token after x402 payment for a bundle.""" bundle = BUNDLE_PRICING[bundle_type] payload = { "wallet": wallet_address, "bundle": bundle_type, "remaining": bundle["requests"], "issued_at": int(time.time()), "expires_at": int(time.time()) + (bundle["validity_days"] * 86400), } return jwt.encode(payload, BUNDLE_SECRET, algorithm="HS256") def validate_bundle_token(token: str) -> Optional[dict]: """Validate and decrement a bundle token. Returns the updated claims if valid, None if expired/invalid. """ try: claims = jwt.decode(token, BUNDLE_SECRET, algorithms=["HS256"]) if claims["expires_at"] < time.time(): return None if claims["remaining"] <= 0: return None claims["remaining"] -= 1 return claims except jwt.InvalidTokenError: return None def refresh_bundle_token(claims: dict) -> str: """Re-issue the token with decremented remaining count.""" return jwt.encode(claims, BUNDLE_SECRET, algorithm="HS256") ``` The documented endpoint then accepts the token and returns a replacement: ```python bundle_token = request.headers.get("x-bundle-token") if bundle_token: claims = validate_bundle_token(bundle_token) if claims: response.headers["X-Bundle-Token"] = r ...[truncated 2881 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill metadata declares credentials named WALLET_ADDRESS and AGENT_SIGNING_KEY, but the code samples consistently read MERCHANT_WALLET_ADDRESS and do not actually consume AGENT_SIGNING_KEY. This mismatch can cause operators to misconfigure deployments, leading to broken payment enforcement or accidental use of the wrong secrets in production.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The guide assures readers that only public wallet addresses are needed and that no private keys are referenced, but later testing and buyer-agent examples require a payer private key or wallet-signing capability. This contradiction can mislead users about secret-handling requirements and cause unsafe key management practices when they discover private signing material is actually needed.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
"scheme": "exact",
      "network": "eip155:8453",
      "maxAmountRequired": "1000000",
      "resource": "https://api.example.com/data/premium",
      "description": "Premium data feed access",
      "mimeType": "application/json",
      "payTo": "0xYourWalletAddress",

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

md
"scheme": "exact",
      "network": "eip155:8453",
      "maxAmountRequired": "1000000",
      "resource": "https://api.example.com/data/premium",
      "description": "Premium data feed access",
      "mimeType": "application/json",
      "payTo": "0xYourWalletAddress",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

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

Extract the PAYMENT-REQUIRED header (base64-encoded):

bash
curl -s -D - http://localhost:3000/api/v1/weather/full 2>&1 | grep -i payment-required
# PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MSwiYWNjZXB0cyI6Wy...

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
96% confidence
Finding

The guide includes a fail-open pattern that serves paid content when the facilitator is unavailable, even though verification did not succeed. In a payment-gated API context, this creates a clear authorization bypass: an attacker can exploit facilitator outages or induced failures to obtain premium content without paying.

Content

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

md
const result = await facilitatorClient.verify(xPayment, requirements);
  if (!result.valid && result.error.includes('Circuit breaker')) {
    // Facilitator is down -- serve content but log for reconciliation
    console.warn('[x402] FAIL OPEN: serving content without verification');
    logUnverifiedPayment(xPayment, requirements);
    return { serve: true, unverified: true };
  }

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The dataset download route is presented as paid content, but the attached dependency is instantiated with price_usd=0 and the promised dynamic override is not applied in the shown route. If copied as written, merchants may unintentionally expose paid datasets for free, causing direct revenue loss and bypass of the intended paywall.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.