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

Content
View full analysis

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
View 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
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
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.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
requests>=2.28.0

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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.