Back to skill

Security audit

Buy a travel eSIM - via x402 USDC Base Wallet

Security checks for vulnerabilities and agentic risk

Overview

The skill openly supports buying eSIMs with USDC, but its payment flow trusts remote payment instructions too much for an irreversible crypto transfer.

Review carefully before installing or using on mainnet. Only use it with a wallet that shows and lets you approve the exact USDC amount, recipient, token contract, and Base network, and prefer testnet until the payment-parameter validation is tightened.

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
esim_api.py:190
Finding
Unvalidated Remote Payment Parameters Can Redirect or Alter USDC Transactions## Vulnerability Details **File Location**: `esim_api.py:190-231` **Vulnerability Type**: Failure to validate security-critical payment parameters **Risk Level**: High ### Vulnerable Code ```python data = response.json() # Parse x402 response - find the "transfer" scheme accepts_list = data.get("accepts", []) accepts = None for scheme in accepts_list: if scheme.get("scheme") == "transfer": accepts = scheme break # Fallback to first scheme if transfer not found if accepts is None: accepts = accepts_list[0] if accepts_list else {} extra = accepts.get("extra", {}) amount_raw = accepts.get("amount", 0) if isinstance(amount_raw, str): amount_raw = int(amount_raw) # Get network info from response network = accepts.get("network", self.config["caip2"]) chain_id = accepts.get("extra", {}).get("chainId", self.config["chain_id"]) return PaymentRequired( nonce=extra.get("nonce", ""), amount_raw=amount_raw, amount_usdc=amount_raw / 1_000_000, pay_to=accepts.get("payTo", ""), asset=accepts.get("asset", ""), network=network, chain_id=chain_id, ) ``` The associated workflow in `SKILL.md:248-252` explicitly directs the caller to use the remotely supplied recipient: ```markdown **Important**: Always use the `payTo` address from the 402 response. Never hardcode payment addresses. ``` ### Technical Analysis The client accepts the recipient address, payment amount, asset contract, network, chain ID, and nonce directly from the remote HTTP 402 response. It does not enforce that: - `network` equals the configured CAIP-2 network. - `chain_id` equals the configured Base Mainnet or Base Sepolia chain ID. - `asset` equals the ...[truncated 2488 chars]
Remediation
## Remediation Suggestions 1. Require an explicit `transfer` scheme and reject the response if none is present. Remove the fallback to the first entry. 2. Validate `network` and `chainId` against the selected entry in `NETWORK_CONFIG`; do not use remote values to override local network selection. 3. Compare `asset` case-insensitively with the configured USDC contract for the selected network. 4. Validate `payTo` and `asset` as 20-byte hexadecimal EVM addresses before presenting transaction instructions. 5. Parse USDC values with `Decimal` or integer smallest units instead of binary floating-point arithmetic. 6. Bind the payment response to the selected package and prior quote. Require the 402 amount to exactly equal the user-confirmed amount. 7. Validate the nonce format and ensure it belongs to the current purchase attempt. 8. Reject missing, zero, negative, malformed, or unexpectedly large payment amounts. 9. Require renewed user confirmation if any payment parameter differs from the previously displayed transaction details. 10. Before signing, display and independently verify the final recipient, token contract, chain, and amount. Wallet policy should enforce spending limits and an allowlist where operationally possible. 11. After payment, verify the transaction receipt, successful token transfer event, recipient, token contract, amount, and chain before submitting the transaction hash as proof.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Vague Triggers

Medium
Confidence
94% confidence
Finding
The invocation guidance is broad enough to trigger the skill for general travel or connectivity-related requests, not just explicit purchase intent. In this skill, invocation can lead into a real-money purchasing workflow on Base Mainnet by default, so an overly eager match increases the risk of the agent steering users into a commercial transaction flow they did not clearly request.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The complete_purchase method sends purchase-related data, including a transaction hash and nonce in the X-PAYMENT header, to a remote service. While the module docstrings describe procurement generally, there is no explicit user-facing warning, confirmation prompt, or visible disclosure at this call site that a real purchase-completion request and associated payment proof are being transmitted.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
Confidence
94% confidence
Finding
The dependency is specified as `requests>=2.28.0`, which allows any future version and does not guarantee a reproducible or reviewed install. This creates supply-chain risk because different environments may resolve to different versions, including versions with newly introduced vulnerabilities or breaking security behavior.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding
The manifest does not pin `requests` to a specific version, so it is impossible to verify whether the installed package includes fixes for known advisories affecting some releases. In practice this means deployments may silently resolve to vulnerable or unsupported versions depending on environment, mirror state, or later package changes.

Static analysis

No suspicious patterns detected.