Skip to main content
This page covers the Rail402 facilitator (/verify, /settle, /supported), the Bazaar catalog and search, the upto settlement contract, the buyer side: the SDK, the rail402 CLI and the rail402-mcp server that let agents pay, and the Playground, a page and server that pay on the testnet from a browser. For each threat it says what Stellar makes different, which mitigations answer it, where they live in the code, which tests prove them, and what risk remains. Every threat is an entry in docs/security/threats.json. The tables below are generated from it, and a unit test fails when a cited source file no longer contains the cited symbol, when a cited test no longer exists, or when a cited conformance case is not declared by its harness. The operational test report shows the exercises that run these tests and what they observed.

Invariants

The design rests on seven properties. Each threat below is an attempt to break one of them.
  1. The facilitator is never the source of payment funds. It sponsors fees through a fee bump and nothing else; any payment that makes a facilitator account a payer, a source or a party to an authorization is refused.
  2. A signed authorization moves funds at most once. The Soroban host consumes its nonce, and Rail402’s settlement ledger answers every repeat from one record.
  3. The payer’s signature fixes the recipient and the amount, or the cap. Changing either after signing fails signature verification on chain.
  4. The sponsor’s exposure is bounded per settlement by the fee ceiling and per hour by the fee budget.
  5. A listing is owned by the payTo that was settled, and is published only after its own origin confirms it.
  6. Rail402 and its buyer tools never dial a non-public address on someone else’s behalf, unless loopback is explicitly allowed for a local test network.
  7. An agent cannot spend beyond the caps its owner set, which are checked immediately before signing.

Assets

Trust boundaries

Summary

STRIDE letters: Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege. “Accepted risk” means the threat cannot be fully closed by a facilitator (for example a payer racing its own payment within one ledger); the residual risk says what bounds it.

Replay

A Stellar payment authorization is not a signed transaction but a Soroban authorization entry: a signature over one contract invocation, a nonce and an expiration ledger, bound to the network by its passphrase. The host consumes the nonce when the entry is used, so funds can move only once. What remains for the facilitator is not to pay for duplicates, to give every repeat the same answer, and to stop a seller from serving one payment many times.

R1. The same authorization is settled twice

Mitigated · Tampering, Elevation of privilege · boundary TB2 (Seller and facilitator) · asset: Payer funds Threat. A seller, a buyer or anyone holding a payment sends the same PAYMENT-SIGNATURE to /settle again, one after another or many at once, hoping the payer is charged more than once. What Stellar makes different. An exact or upto authorization is a Soroban authorization entry whose nonce the host consumes when the entry is used; a second use fails on chain with Error(Auth, ExistingValue). The nonce lives under the payer’s own address, for G and C accounts alike, until the signature expires. A duplicate that reaches the network is included and fails, so the sponsor still pays its fee. Preconditions. A copy of a payment payload that was already sent to /settle. Impact. Without protection: one wasted sponsor fee per duplicate. The payer can never be charged twice, because the host refuses the second use of the nonce. Mitigations Tests Conformance runs. live-matrix case replay-idempotent, live-matrix case replay-concurrent, live-matrix case replay-late. Residual risk. None for funds. A repeat costs a database read, and rate limits bound how often it can be asked.

R2. The same authorization in a new envelope

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Sponsor XLM Threat. An attacker wraps an already settled authorization entry in a transaction of different bytes (another source, sequence or fee) so it is not recognised as a repeat. What Stellar makes different. The authorization is bound to its invocation, not to the transaction, so it can be placed in any envelope. The host refuses the consumed nonce at apply time; a facilitator that only compared envelope bytes would submit it and pay the fee. Preconditions. A copy of a settled authorization entry. Impact. One wasted sponsor fee per attempt if it reached the network. Mitigations Tests Conformance runs. live-matrix case replay-rejected. Residual risk. None: nothing is submitted.

R3. An authorization replayed on another network

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Payer funds Threat. A payment signed for stellar:testnet is presented for stellar:pubnet, or the reverse. What Stellar makes different. The signed preimage (HashIdPreimageSorobanAuthorization) includes the network id, the SHA-256 of the network passphrase, so a signature is only valid on the network it was made for. Preconditions. A signed payment for one network. Impact. None on chain: signature verification fails on the other network. Mitigations
  • The network id is part of every signed preimage the buyer’s guard decodes and checks. (packages/stellar/src/auth.ts, networkIdForPassphrase)
  • A request is served only on the network its requirements name, and a payload for another network is refused. (apps/rail402/src/app.ts, invalid_network)
Tests Conformance runs. rejection-audit case unknown-network. Residual risk. None.

R4. Paid once, served many times

Accepted risk · Spoofing, Elevation of privilege · boundary TB1 (Buyer and seller) · asset: Seller revenue Threat. A buyer pays once and sends the same PAYMENT-SIGNATURE several times, concurrently or later. A stock upfront seller calls /settle without /verify and serves on success, and an idempotent facilitator answers every repeat with the same success. What Stellar makes different. Nothing Stellar-specific allows it: it follows from answering repeats with the stored result, which the x402 idempotency requirement asks for. The Stellar part is that the answer carries the one settlement transaction hash, which a seller can deduplicate on. Preconditions. A seller that serves on a successful /settle without remembering which settlements it already served. Impact. The seller serves a paid resource more than once for one payment. The buyer is charged once. Mitigations Tests Conformance runs. live-matrix case replay-idempotent. Residual risk. An upfront seller on stock middleware that does not wrap its facilitator client with withSettleDedupe, or deduplicate PAYMENT-RESPONSE.transaction itself, can serve a repeat. The seller helpers page tells upfront sellers to wrap it.

R5. An upto authorization settled for zero stays usable

Mitigated · Tampering · boundary TB7 (Contracts) · asset: Payer funds Threat. A seller settles an upto payment for zero, then tries to charge the same authorization again for a positive amount. What Stellar makes different. An on-chain zero settlement still uses the payer’s authorization entry, so the host consumes its nonce like any other settlement. An off-chain zero (UPTO_ZERO_SETTLEMENT=offchain) sends nothing, so the nonce stays unused on chain until the signature expires. Preconditions. An upto payment settled for zero. Impact. Without protection, a second settlement of the same authorization for up to the cap. Mitigations
  • The upto contract consumes the payer’s nonce for a zero settlement too. (contracts/stellar/upto/src/lib.rs, pub fn settle)
  • An off-chain zero is a terminal record in the settlement ledger, so a later request for any amount is answered from it or refused as a conflict; only the named authority can settle on chain, and it is Rail402’s. (packages/facilitator/src/upto-scheme.ts, offchainZero)
Tests Conformance runs. live-matrix case upto-zero. Residual risk. With off-chain zeros and STORE=memory, a restart forgets the record; the authorization then stays settleable by Rail402’s own authority until it expires. Use Postgres wherever off-chain zeros are enabled.

Front-running

Because the authorization is bound to its invocation rather than to a transaction, anyone who sees it before it lands can submit it in a transaction of their own. That can only make the signed payment happen, exactly as signed; it cannot redirect it. The cost is Rail402’s own failed transaction, and the risk is telling a seller “failed” for a payment that settled.

F1. A copied exact authorization lands first

Accepted risk · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Sponsor XLM Threat. Someone who sees the settlement envelope (the seller, an RPC operator, a validator’s pending queue) copies the payer’s authorization entry into a transaction of their own and gets it included before Rail402’s. What Stellar makes different. A Soroban authorization is a bearer instrument for exactly one invocation: whoever submits it can only make the signed transfer happen, from the payer to payTo for the signed amount. It cannot be redirected. Rail402’s own transaction then fails on the consumed nonce and its fee is spent. Preconditions. Access to the envelope between signing and inclusion, and an account to pay the copy’s fee. Impact. One wasted sponsor fee per event. The attacker pays its own fee and gains nothing; the payment settles as signed. Mitigations Tests Residual risk. The wasted fee is not recoverable. Each event costs the attacker its own fee too.

F2. The seller is told the payment failed although it settled

Mitigated · Repudiation · boundary TB2 (Seller and facilitator) · asset: Seller revenue Threat. After a front-run, Rail402’s transaction fails with a consumed nonce and the seller is told the payment failed, although the payer was charged. What Stellar makes different. The chain holds the proof: the nonce entry’s last-modified ledger, and in that ledger a successful transaction whose address credential (payer, nonce, signature expiration) and root invocation are byte-equal to the payer’s signed entry, with the matching token transfer event. Preconditions. A front-run as in F1. Impact. Without reconciliation the seller withholds a resource that was paid for. Mitigations
  • A failed settlement whose nonce was consumed is reconciled: Rail402 finds the other transaction on chain, proves it carried the same signed invocation after the request was claimed, and answers success with its hash. (packages/facilitator/src/third-party.ts, findThirdPartySettlement)
  • Proof requires a byte-equal root invocation and credential; a different invocation for the same nonce stays a failure. (packages/facilitator/src/third-party.ts, signedAuthorizationOf)
Tests Residual risk. A public RPC keeps only a window of ledgers; a settlement reconciled after that window stays failed. Reconciliation runs immediately after the failure, well inside the window.

F3. A third party settles an upto payment for another amount by submitting it

Mitigated · Tampering, Elevation of privilege · boundary TB7 (Contracts) · asset: Payer funds Threat. Someone holding an upto payload (the buyer, the seller, a TLS-terminating proxy, a log, an RPC operator who sees the settlement envelope) submits it themselves for another amount up to the cap, such as zero or the whole cap, before or instead of the seller’s settlement. What Stellar makes different. The payer’s signature covers the cap, not the amount. The upto contract therefore also requires the authorization of the settlement authority named in the signed terms, and Rail402 makes that authority the operation’s source account, so its authorization is the transaction’s own signature and cannot be copied into another transaction. Preconditions. A copy of an upto payload. Impact. None: the network refuses a settlement that the settlement authority did not authorize, and Rail402’s settlement proceeds. The same request through the facilitator’s own /settle is F5. Mitigations
  • The contract requires terms.facilitator to authorize settle(payer, terms, amount), so the authority’s authorization covers one amount and nothing else. (contracts/stellar/upto/src/lib.rs, terms.facilitator.require_auth())
  • Rail402 signs as the authority through a source-account credential that exists only inside its own transaction. (packages/upto/src/terms.ts, authorityEntry)
Tests Residual risk. None against anyone without the settlement authority’s key; a holder of that key is U10.

F4. Another holder of the authority settles first

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Seller revenue Threat. Two replicas sharing the sponsor secret, or a stale process, settle the same upto payment; one lands first with a different amount than the seller asked for. What Stellar makes different. Only a transaction signed by the authority can land; the landed amount is readable from the settle arguments and the upto_settled event. Preconditions. Two processes that hold the same authority key. Impact. Without reconciliation the seller gets a false failure, or a success for an amount it did not request. Mitigations
  • The later settlement is answered with the landed transaction when it settled the same amount, and with settle_upto_stellar_settled_elsewhere, naming both amounts, when it did not. (packages/facilitator/src/engine.ts, settle_upto_stellar_settled_elsewhere)
Tests Residual risk. None beyond one wasted fee.

F5. A payload holder fixes the upto amount through the facilitator’s /settle

Accepted risk · Tampering · boundary TB2 (Seller and facilitator) · asset: Seller revenue Threat. Someone holding an upto payload asks the facilitator’s own /settle to settle it for another amount up to the cap, such as zero or the whole cap, before the seller does. What Stellar makes different. The payer’s signature covers the cap, not the amount, and the facilitator is the settlement authority: whichever accepted /settle request comes first decides the amount it authorizes, and the payer’s nonce then makes the authorization single-use. Preconditions. A copy of an upto payload and access to the facilitator’s /settle (keyless on the public testnet service). Impact. The first accepted request fixes the amount anywhere in [0, cap] to payTo: a buyer who settles zero first leaves the seller unpaid for that request, and anyone who settles the cap first charges the buyer the full cap. The seller’s own later request for a different amount is refused as an idempotency conflict. Funds never go elsewhere and never exceed the cap. Mitigations
  • The facilitator settles an authorization once: the first accepted amount wins, and a later request for a different amount is refused with settle_upto_stellar_idempotency_conflict naming the amount that holds it. (packages/facilitator/src/engine.ts, idempotencyConflict)
  • Operators can require API keys for /verify and /settle per network, so only known sellers can settle; the payload travels only from buyer to seller over TLS, and sellers settle before releasing a buffered response. (apps/rail402/src/app.ts, requireApiKey)
Tests Residual risk. Accepted for payload holders on a keyless facilitator: a buyer or anyone who sees the payload can fix the amount through /settle before the seller (unpaid seller compute and one sponsor fee, or the buyer charged up to its own cap). Operators who need more require API keys for /settle.

Fee sponsorship

The sponsor pays the fee of every transaction it submits, and Soroban charges resource fees whether the transaction succeeds or fails. Every refusal Rail402 can make before signing costs nothing; a transaction that is included and fails costs its fee. Rail402 refuses whatever it can see coming, names and counts what it cannot, and bounds the total.

S1. A payment passes simulation and fails at apply

Accepted risk · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Sponsor XLM Threat. A payer spends the balance, or changes a smart account’s state, after the settle-time simulation and before inclusion, so the settlement is included and fails. What Stellar makes different. Soroban resource fees are charged when a transaction is included, success or failure; the sponsor pays through the fee bump. Simulation runs against the latest ledger, so a change made in the ledgers between simulation and inclusion is not seen. Preconditions. A payer (or its smart-account policy) able to act within about one ledger of the settlement. Impact. One wasted sponsor fee per event, never more than the fee ceiling. No funds move. Mitigations Tests Residual risk. The race inside one ledger cannot be closed by a facilitator that does not hold the funds. Each event costs the payer an on-chain transaction of its own.

S2. An authorization expires between submission and inclusion

Accepted risk · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Sponsor XLM Threat. A payment is settled with an authorization whose expiration ledger passes while the transaction waits for inclusion. What Stellar makes different. Validity runs through signatureExpirationLedger inclusive; the transaction’s time bound can outlive it. An included transaction whose authorization expired fails and its fee is spent. Preconditions. A payment settled at the very end of its validity window. Impact. One wasted sponsor fee. Mitigations
  • Settle refuses an authorization that expires within a one-ledger margin of the current ledger. (packages/facilitator/src/preflight.ts, invalid_exact_stellar_signature_expiration_too_soon)
  • An upto settlement’s ledger bounds end at the expiration ledger, so the network refuses it before inclusion and no fee is spent. (packages/upto/src/facilitator/scheme.ts, buildTransaction)
Tests Conformance runs. live-matrix case expiry-window. Residual risk. For exact, a transaction that waits longer than the margin in the network’s queue can still fail this way; it is named and counted as wasted.

S3. A payment that is expensive to settle

Mitigated · Denial of service · boundary TB2 (Seller and facilitator) · asset: Sponsor XLM Threat. A payer submits a payment whose settlement needs large resources: a heavy __check_auth, a token that writes a lot of state, or rent for entries it extends. What Stellar makes different. Soroban fees depend on the instructions, ledger reads and writes, and rent of the invocation, which the payer’s account and the token contract choose. The fee is known from simulation before anything is signed. Preconditions. A smart account or token contract whose calls are costly. Impact. Without a ceiling, an arbitrary fee per settlement. Mitigations Tests Residual risk. A payment just under the ceiling costs up to the ceiling. Operators accepting tokens that pay rent on read raise the ceiling knowingly (the TEST6 token needs about 1,000,000 stroops on testnet).

S4. A flood of valid small payments

Accepted risk · Denial of service · boundary TB2 (Seller and facilitator) · asset: Sponsor XLM Threat. Someone makes many real, valid payments of the smallest amount to drain the sponsor through fees. What Stellar makes different. Every valid settlement costs the sponsor a fee of tens of thousands of stroops, far more than a one-unit USDC payment is worth. Preconditions. Funds for many small payments. Impact. Sponsor XLM spent at the attacker’s pace. Mitigations
  • An hourly fee budget, shared by replicas through the settlement ledger, stops new settlements once reached; pubnet refuses to start without one. (packages/facilitator/src/guard.ts, maxSpendPerHourStroops)
  • Per-client rate limits and API keys on /verify and /settle; API keys must be decided explicitly for pubnet. (apps/rail402/src/rate-limit.ts, RateLimiter)
  • Per-asset minimum amounts for exact payments and upto caps (an upto settlement may still be zero; see U6). (apps/rail402/src/config.ts, parseAssets)
Tests Conformance runs. rejection-audit case rate-limited, rejection-audit case api-key-required. Residual risk. On a free, keyless testnet facilitator the per-client rate limit (IPv6 clients are grouped by /64) and, where configured, the hourly budget are the only bounds. A client with many addresses passes the rate limit, so the budget is the real bound: once it is spent, every settlement is refused until the hour moves on, which trades availability for the sponsor’s balance. On pubnet the budget is required, and pricing, keys and minimums are an operator decision.

S5. The sponsor falls below its reserve

Mitigated · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Availability Threat. The sponsor’s balance runs low, from normal use or an attack, until fee bumps start failing mid-settlement. What Stellar makes different. The sponsor also carries the base reserves of the channel accounts and the upto authority, which it sponsors; below them it cannot pay fees at all. Preconditions. Sustained settlement volume or an attack as in S4. Impact. Settlements fail after signing; the service becomes unavailable. Mitigations Tests Residual risk. None beyond downtime until the sponsor is refunded.

S6. Duplicate submissions of one settlement

Mitigated · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Sponsor XLM Threat. Retries, concurrent requests or two replicas submit the same settlement more than once. What Stellar makes different. Each submitted transaction consumes a channel sequence number and pays its own fee; the second one fails on the consumed nonce. Preconditions. Concurrent or repeated requests for one payment. Impact. Wasted fees and sequence conflicts. Mitigations Tests Conformance runs. concurrency case distinct-sequences. Residual risk. None.

S7. Restoring archived state inside a buyer’s payment

Mitigated · Denial of service · boundary TB7 (Contracts) · asset: Sponsor XLM Threat. A settlement touches an archived contract instance or entry, and the transaction would restore it, billing the restoration rent to the sponsor as part of the payment. What Stellar makes different. Since protocol 23 a freshly simulated invocation of an archived persistent entry restores it automatically inside the transaction, and the fee payer pays the rent. Preconditions. An archived settlement contract, token instance or balance entry. Impact. Rent paid by the sponsor on behalf of whoever let the entry expire. Mitigations
  • A simulation that reports archived state is refused: the upto settlement contract with invalid_upto_stellar_settlement_contract_archived, other entries with the archived-state code; nothing is submitted. (packages/upto/src/facilitator/simulation.ts, archivedStateRejection)
  • Exact payments name archived state from the simulation’s restore preamble. (packages/facilitator/src/explain.ts, explainSimulation)
Tests Residual risk. None; restoration is an operator action (U7).

Catalog poisoning

The Bazaar catalogs resources automatically after a successful settlement. The settlement proves one thing, that a payment reached payTo; everything else in the resource description came from the buyer’s request and must be confirmed at the resource’s own origin before it is published.

P1. Forged listing metadata or price

Mitigated · Spoofing, Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A buyer (or a seller’s buggy client) echoes a resource block with a false description, price or schema in the payment, hoping the catalog publishes it. What Stellar makes different. Nothing on chain describes the resource; the settlement proves only that a payment to payTo happened. The metadata must be confirmed at its origin. Preconditions. One real settlement to the seller. Impact. Misleading listings shown to buyers and agents. Mitigations
  • An HTTP resource, or an MCP tool at an http(s) endpoint, is published only after its own 402 (for a tool, the answer to an unpaid tools/call over MCP) confirms payTo, price and metadata; what is published always comes from that answer, so echoed metadata never changes a listing on its own. (packages/bazaar/src/catalog.ts, checkOrigins)
  • MCP tools are asked through safe-fetch with the MCP client: the tool must be declared in tools/list and its unpaid call must answer with the x402 PaymentRequired. (packages/bazaar/src/mcp-origin.ts, probeMcpTool)
Tests Conformance runs. live-bazaar case forged-metadata, live-bazaar case not-x402. Residual risk. A seller can describe its own resource falsely; buyers bind the price and recipient of a listing when paying (M6). Tools under an mcp:// URL have no origin to check (P10).

P2. Taking over another seller’s listing

Mitigated · Spoofing, Elevation of privilege · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. An attacker pays a URL that belongs to another seller, but to its own payTo, to take over or modify that listing. What Stellar makes different. The settled payTo is the only identity the facilitator has proven, from a settlement that moved value to it. An upto zero moves nothing and proves nothing, so it never catalogs. Preconditions. A settlement for the victim’s URL to the attacker’s account. Impact. Payments for a listed resource redirected to the attacker. Mitigations
  • A listing is bound to the settled payTo; another payTo is refused with bazaar_owner_conflict, and ownership moves only when the origin names the new payTo. (packages/bazaar/src/codes.ts, bazaar_owner_conflict)
  • A settlement that moved nothing (an upto zero, on-chain or off) never catalogs. A settlement that moved one base unit does not help either: a listing is published only with what its own origin answers (P1, P9). (packages/bazaar/src/catalog.ts, unprovenSettlement)
Tests Conformance runs. live-bazaar case owner-conflict. Residual risk. None for resources with an origin. A tool under an mcp:// URL is scoped to its owner, so it cannot be taken over, but its first content is whatever its first payer echoed (P10).

P3. Hostile JSON Schema in listing metadata

Mitigated · Denial of service, Information disclosure · boundary TB2 (Seller and facilitator) · asset: Availability Threat. Metadata carries a schema with a catastrophic regular expression, a combinatorial check, deep nesting or an external $ref. What Stellar makes different. None: this is a property of the catalog, which validates schemas it receives in payments. Preconditions. One settlement carrying the schema. Impact. Catalog workers stalled, or the service fetching arbitrary URLs. Mitigations Tests Conformance runs. live-bazaar case schema-rejected. Residual risk. None.

P4. Encoded traversal in a route template

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A routeTemplate hides ../, NUL, CR/LF or a backslash behind percent-encoding so that a listing matches, or later fills, paths it should not. What Stellar makes different. Nothing in the parsing is Stellar-specific. What a template changes is which paths are paid to one Stellar account: a listing and every path its template claims stay bound to the payTo proven by the settled transfer (P2), so a template can only widen a seller’s own listing, and only over paths its own route matched. Preconditions. One settlement carrying the template. Impact. A listing that claims paths of another resource, or a buyer tool that fills a path outside the seller’s route. Mitigations Tests Conformance runs. live-bazaar case route-template. Residual risk. None.

P5. Wash settlements to flood or rank the catalog

Accepted risk · Tampering, Denial of service · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A seller pays itself through a second account many times to create many listings or to look popular. What Stellar makes different. Settlements are cheap on Stellar, so volume is a weak signal of quality. Preconditions. Two accounts and a little USDC. Impact. Catalog spam and manipulated rankings. Mitigations Tests Residual risk. A patient seller can still create listings up to the limits; each needs a working origin that answers 402.

P6. One payment used for many listings

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. The same settled transaction is presented with different resource blocks to catalog several resources. What Stellar makes different. A transaction hash identifies exactly one settlement on chain. Preconditions. One settlement. Impact. Listings without payments behind them. Mitigations Tests Conformance runs. live-bazaar case settlement-reused, live-bazaar case duplicate-settlement. Residual risk. None.

P7. Injection through reasons and response headers

Mitigated · Tampering, Denial of service · boundary TB2 (Seller and facilitator) · asset: Availability Threat. Metadata crafted so that a rejection reason quoting it breaks headers, logs or clients, or grows EXTENSION-RESPONSES beyond what proxies accept. What Stellar makes different. EXTENSION-RESPONSES travels in the same /settle answer as the settled transaction hash. The payment is final on the ledger whatever happens to the header, so a header that breaks the answer would hide a settled payment from the seller; the bound keeps that answer intact. Preconditions. One settlement with crafted metadata. Impact. Broken settle responses or log injection. Mitigations Tests Conformance runs. live-bazaar case header-bounded. Residual risk. None.

P8. Duplicate listings through URL spellings

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A seller or attacker lists one resource many times by varying case, ports, dot segments or percent-encoding. What Stellar makes different. A spelling variant would be a second key with no owner, escaping the binding to the payTo proven on chain (P2). Listings are keyed by the canonical URL per network (stellar:testnet, stellar:pubnet), so one resource has one owner per network. Preconditions. Settlements for the variant URLs. Impact. Duplicate results, and a second listing that escapes the owner binding of the first. Mitigations
  • URLs are canonicalised (case, default ports, dot segments, percent-encoding, query and fragment) before they key a listing. (packages/bazaar/src/url.ts, canonicalizeUrl)
Tests Residual risk. None.

P9. Planting a seller’s MCP tool listing

Mitigated · Spoofing, Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A payer pays a seller’s payTo one base unit (or settles one unit under an upto cap of any size) while echoing the seller’s MCP endpoint and tool name with its own description, input schema or price, hoping to publish, freeze or domain-verify that content under the seller’s identity, or to use up the seller’s quota of new listings. What Stellar makes different. The settlement proves a payment to payTo, not who wrote the echoed tool description. Dust costs the payer almost nothing and the sponsor pays the fee, so a settlement alone cannot vouch for content. Preconditions. A trustline and one base unit of the asset; the seller’s MCP endpoint and tool name. Impact. Spoofed tool descriptions (prompt injection) and prices shown to agents under the seller’s verified identity; the seller’s own tools not cataloged. Mitigations
  • A tool at an http(s) endpoint stays pending until the tool itself answers an unpaid tools/call with a PaymentRequired naming the owner; until an origin check has confirmed it, the tool must also offer the settled scheme, network, asset, payTo and price (cap), or the listing is quarantined with origin_payment_mismatch. (packages/bazaar/src/catalog.ts, samePayment)
  • A later settlement the tool itself asked for is checked again and replaces planted content with the tool’s own; a planted settlement cannot change or withdraw a confirmed tool. (packages/bazaar/src/catalog.ts, checkOrigins)
  • Domain trust is granted only to origin-verified listings, and needs the two-way SEP-1 binding. (packages/bazaar/src/catalog.ts, enrich)
  • Only listings the owner’s origin confirmed count toward the owner’s hourly quota. (packages/store-postgres/src/catalog-store.ts, countCreatedBy)
Tests Conformance runs. live-bazaar case mcp-tool. Residual risk. Tools under an mcp:// URL (P10). A planted settlement can quarantine a tool that was never confirmed until the tool’s next real settlement.

P10. Unverifiable content of mcp:// tools

Accepted risk · Spoofing, Tampering · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. A tool under an mcp:// URL names no server, so nothing but the settlement vouches for its description, schema and price; whoever first pays its owner for it chooses them. What Stellar makes different. As P9: a settlement proves the payment, not the content. Preconditions. One settlement of one base unit to the owner, before the tool’s first real settlement. Impact. A misleading description or price on the owner’s mcp:// tool listing. Mitigations
  • mcp:// tools are scoped to their owner and published at trust settled, which they never leave: no domain can vouch for them. (packages/bazaar/src/catalog.ts, verifiable)
  • The Rail402 SDK does not call mcp:// resources, and its default pubnet policy pays MCP listings only at domain_verified. (packages/sdk/src/policy/policy.ts, resolvePolicy)
Tests Residual risk. The first content of an mcp:// tool is chosen by its first payer and kept. Sellers who want their own content listed serve their tools at an http(s) endpoint.

SSRF

Cataloging makes the facilitator fetch URLs that sellers choose, and paying makes an agent fetch URLs that a listing or a prompt suggests. Both run through a fetch that resolves the host itself, refuses non-public addresses at dial time, follows no redirect it has not re-checked, and caps sizes and times.

X1. Origin checks to private or metadata addresses

Mitigated · Information disclosure, Elevation of privilege · boundary TB5 (Facilitator and the open internet) · asset: Operator infrastructure Threat. A listing’s resource URL points at a private, loopback, link-local or cloud metadata address, so the catalog’s origin check reaches the operator’s internal network. What Stellar makes different. Cataloging runs after any settlement, so anyone who can pay a cent can make the facilitator fetch a URL. Preconditions. One settlement with a crafted resource URL. Impact. Requests from the facilitator’s network position; internal data in listing metadata. Mitigations
  • Hosts are resolved and every address checked against a block list at dial time, IP literals before any lookup. (packages/bazaar/src/origin.ts, safeLookup)
  • Private and loopback addresses are refused unless DISCOVERY_ALLOW_LOOPBACK is set, which is refused together with pubnet. (apps/rail402/src/config.ts, DISCOVERY_ALLOW_LOOPBACK)
Tests Residual risk. None known. The RPC URL is operator configuration and trusted.

X2. DNS rebinding

Mitigated · Information disclosure, Elevation of privilege · boundary TB5 (Facilitator and the open internet) · asset: Operator infrastructure Threat. A hostname answers with a public address when checked and a private one when connected. What Stellar makes different. The SEP-1 domain check fetches stellar.toml from a host the seller names, so a seller’s own DNS zone is the natural rebinding vector. That fetch uses the same pinned lookup as origin checks. Preconditions. Control of a DNS zone. Impact. As X1. Mitigations
  • The address that is checked is the address that is dialled: the lookup used by the connection validates every answer. (packages/safe-fetch/src/lookup.ts, pinnedLookup)
Tests Residual risk. None.

X3. Redirects to internal hosts

Mitigated · Information disclosure, Elevation of privilege · boundary TB5 (Facilitator and the open internet) · asset: Operator infrastructure Threat. A public origin answers with a redirect to a private address. What Stellar makes different. Rail402 reads stellar.toml only from the listing’s own host and follows no redirect for it, so a seller cannot point the SEP-1 check at an internal address. Preconditions. Control of a public origin. Impact. As X1. Mitigations
  • Origin checks and SEP-1 fetches follow no redirects; buyers follow only same-origin redirects, re-checking each hop, and none after paying. (packages/safe-fetch/src/redirect.ts, nextHop)
Tests Conformance runs. rejection-audit case mcp-redirect-private. Residual risk. None.

X4. Oversized or slow answers

Mitigated · Denial of service · boundary TB5 (Facilitator and the open internet) · asset: Availability Threat. An origin answers with a huge, compressed or endless body, or trickles bytes to hold connections open. What Stellar makes different. SEP-1 limits stellar.toml to 100 KB; Rail402 enforces that limit on the decoded file. Preconditions. Control of an origin. Impact. Memory exhaustion or stalled workers. Mitigations Tests Residual risk. None.

X5. stellar.toml fetches

Mitigated · Information disclosure · boundary TB5 (Facilitator and the open internet) · asset: Operator infrastructure Threat. The domain check fetches /.well-known/stellar.toml from a host the seller chose. What Stellar makes different. SEP-1 lets a domain claim Stellar accounts; Rail402 reads ACCOUNTS, and the owner’s home_domain, to raise an origin-verified listing’s trust to domain_verified only when both name each other, which means fetching from the seller’s host. Preconditions. One listed resource. Impact. As X1. Mitigations Tests Residual risk. None.

X6. Paid calls to agent-chosen URLs

Mitigated · Information disclosure, Elevation of privilege · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. An agent, possibly steered by text in a listing, asks pay_and_call (or the SDK or CLI) to call a private address, sending a payment or the agent’s request there. What Stellar makes different. None: the buyer’s egress runs on the agent’s machine. Preconditions. An agent that follows URLs from untrusted content. Impact. Requests to the agent’s internal network, with a payment header attached. Mitigations
  • Every buyer request goes through safe-fetch: public destinations only, the address pinned at dial time, redirects re-checked. (packages/safe-fetch/src/fetch.ts, createSafeFetch)
Tests Conformance runs. rejection-audit case mcp-private-address. Residual risk. None.

Smart accounts

A C… payer is a contract account whose __check_auth decides whether a payment is authorized. Nothing about it can be checked offline: only executing it tells, and its answer can change between simulation and inclusion. Rail402 simulates in enforcing mode at verify and again at settle; policy composition with upto is proved in contract tests with OpenZeppelin smart accounts.

A1. A payment signed by a key the account does not accept

Mitigated · Spoofing · boundary TB7 (Contracts) · asset: Payer funds Threat. Someone signs a C-account payment with a key that is not one of the smart account’s signers. What Stellar makes different. A contract account authorizes through its own __check_auth; only executing it can tell whether a signature is valid. There is no signature a facilitator could check offline. Preconditions. Knowledge of the smart account’s address. Impact. None if detected; the account’s __check_auth refuses. Mitigations
  • Verify and settle both run an enforcing simulation, which executes __check_auth; a refusal is named smart_account_rejected. (packages/stellar/src/diagnostics.ts, classifyTransferFailure)
Tests Conformance runs. live-matrix case c-payer-wrong-key, rejection-audit case upto-c-payer-wrong-key. Residual risk. None.

A2. __check_auth that passes simulation and fails at apply

Accepted risk · Denial of service · boundary TB7 (Contracts) · asset: Sponsor XLM Threat. A smart account whose __check_auth depends on state it can change (a nonce, a time, a policy budget) passes simulation and fails at inclusion, deliberately. What Stellar makes different. __check_auth is arbitrary contract code. Its result at apply time can differ from simulation, and the included, failed transaction’s fee is the sponsor’s. Preconditions. A smart account the attacker controls. Impact. One wasted sponsor fee per payment. Mitigations Tests Residual risk. Like S1, the race within one ledger cannot be closed. A per-payer limit on on-chain failures is not built; the hourly budget is the bound.

A3. Policy events refuse exact payments from policy accounts

Accepted risk · Denial of service · boundary TB7 (Contracts) · asset: Availability Threat. A smart account with a policy that emits its own events (such as a spending limit recording a spend) pays with exact. What Stellar makes different. The upstream @x402/stellar exact verifier accepts only the token’s transfer event in the simulation and refuses any other contract event as invalid_exact_stellar_payload_event_not_transfer. The upto verifier accepts events other than balance changes, so policy events do not refuse upto payments. Preconditions. A payer whose __check_auth publishes events. Impact. Such accounts cannot pay with exact through any facilitator built on the upstream verifier; nothing moves and the sponsor pays nothing. Mitigations Tests. None: this is a documented limit, not a behaviour Rail402 implements. Residual risk. An upstream limit of the exact scheme; it fails closed. Policy accounts pay with upto.

A4. A spending limit that does not cover upto

Mitigated · Elevation of privilege · boundary TB7 (Contracts) · asset: Agent budgets Threat. An agent key constrained by a spending-limit policy pays with upto, whose settlement reaches the account as an approve rather than a transfer, and slips past a limit that only counts transfers. What Stellar makes different. The upto contract spends through approve(payer, contract, cap, expiration) and transfer_from, so the account’s __check_auth sees an approve context for the full cap. Preconditions. A smart account whose policy budgets transfer only. Impact. Spending beyond the account owner’s budget. Mitigations Tests Conformance runs. live-matrix case upto-c-over-limit. Residual risk. OpenZeppelin’s stock spending-limit policy refuses the approve context outright; accounts that use it cannot pay with upto.

A5. Bypassing the policy through the Default rule

Mitigated · Elevation of privilege · boundary TB7 (Contracts) · asset: Agent budgets Threat. An agent key picks a context rule without the spending policy (the Default rule, or the policy-free upto rule for the approve), or a relayer moves a signed payment to another rule. What Stellar makes different. In OpenZeppelin smart accounts the signer chooses the rule per context, so a key that appears on a rule without the policy can spend without it. Preconditions. An agent key listed on a rule without the policy. Impact. Spending beyond the budget. Mitigations
  • The documented rule configuration keeps the agent key off every rule without the policy, and the signature covers the rule ids. (contracts/testing/README.md, Rule configuration for an agent key)
Tests Residual risk. Depends on the account owner configuring rules as documented.

A6. Concurrent payments beyond the account’s own budget

Accepted risk · Denial of service · boundary TB7 (Contracts) · asset: Sponsor XLM Threat. Several payments from one policy account are verified at once; together they exceed the account’s budget, and the later ones fail at apply. What Stellar makes different. The policy’s budget is on-chain state updated at apply time; simulations of concurrent payments each see the budget before the others. Preconditions. A policy account paying several sellers at once. Impact. Wasted sponsor fees for the payments that fail. Mitigations Tests Residual risk. As S1.

MCP paid calls

rail402-mcp, the SDK and the CLI hold a signing key on the agent’s side. The threats are about the agent’s money: paying more than allowed, paying the wrong party, leaking the key, paying twice, and following URLs it should not.

M1. An agent spends beyond its caps

Mitigated · Elevation of privilege · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. A model, a prompt injection or a bug makes an agent pay more per call or per session than its owner allowed, including through many concurrent calls or several processes. What Stellar makes different. A signed authorization is spendable by whoever holds it until it expires, so the cap must be enforced before signing, not after. Preconditions. An agent with a signing key and a budget. Impact. Loss of the agent owner’s funds up to the key’s balance. Mitigations
  • A guarded signer checks the per-call cap and reserves the session budget immediately before signing, and signs only the payment it approved. (packages/sdk/src/policy/guard.ts, createGuardedSigner)
  • The budget ledger reserves atomically, also across processes sharing one SQLite file. (packages/sdk/src/policy/ledger.ts, checkReservation)
Tests Conformance runs. rejection-audit case mcp-per-call-cap, rejection-audit case mcp-over-ceiling. Residual risk. The caps bound the loss; anything within them the agent may spend.

M2. Payments on networks, assets or to recipients not allowed

Mitigated · Elevation of privilege · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. A seller’s 402 asks for another network, another asset, or a recipient outside the allow list. What Stellar makes different. Assets are contracts; the same symbol can be issued by anyone. The policy therefore compares contract ids and network passphrases, not symbols. Preconditions. A seller offering unexpected terms. Impact. Payment in the wrong asset or to the wrong party. Mitigations
  • Spend decisions check the network in the signed preimage, the asset contract and the recipient against the policy. (packages/sdk/src/policy/decide.ts, decideSpend)
Tests Conformance runs. rejection-audit case mcp-network-not-permitted, rejection-audit case mcp-asset-not-permitted, rejection-audit case mcp-recipient-not-permitted. Residual risk. None.

M3. Secrets in tool schemas, results, errors or logs

Mitigated · Information disclosure · boundary TB6 (Agent, MCP server and paid URLs) · asset: Secrets Threat. The signing seed, its raw bytes, a signature or a payment header leaks into an MCP tool schema, a tool result, a protocol error, a log line, the budget ledger or the CLI’s output. What Stellar makes different. A Stellar seed (S…) or its 32 raw bytes is the whole key; a leaked signed authorization is spendable until it expires. Preconditions. Any error or log path that echoes inputs, or a seller that echoes the payment into its answer. Impact. Loss of the agent’s funds. Mitigations
  • Errors and results are redacted and sanitised; loggers redact secret-like fields. (packages/sdk/src/redact.ts, redactDeep)
  • Tool schemas are published without secret-like properties or header mirroring. (packages/mcp/src/schemas.ts, publishedSchema)
  • A seller that echoes its request cannot hand the payment back: a paid answer is returned and stored with the call’s payment header, payload, signed entries and signatures replaced. (packages/sdk/src/flow/scrub.ts, scrubOutcome)
Tests Residual risk. The key lives in the agent’s process; a compromised host is out of scope.

M4. A retry pays twice

Mitigated · Tampering · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. An agent retries a paid call after a timeout or crash and pays again for the same work. What Stellar makes different. Each new authorization carries a fresh nonce, so a retry signed anew is a second payment. Preconditions. A retried tool call. Impact. Double payment. Mitigations
  • Calls carry an idempotency key; a repeated key replays the stored outcome without signing, and the payment identifier travels to the seller. (packages/sdk/src/flow/common.ts, replayStored)
  • A repeated key first reconciles its own payment on chain: it reports a consumed authorization as paid, and frees the key for a new payment only once the chain shows the authorization expired unused. (packages/sdk/src/flow/common.ts, replayStored)
Tests Residual risk. A retry without the same key is a new payment by design.

M5. Prompt-injected listings and bait-and-switch sellers

Accepted risk · Spoofing, Tampering · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. A listing’s text steers the agent, or the seller’s live 402 asks for a different recipient or a higher price than its listing. What Stellar makes different. The listing’s payTo is the account the facilitator saw settled; the live 402 is not bound to it unless the buyer checks. Preconditions. A malicious or changed seller. Impact. Payment to an unexpected party or at an unexpected price. Mitigations Tests Residual risk. An agent still reads seller-controlled text; the caps (M1) bound what it can spend.

M6. A tampered payment or a false settlement report

Mitigated · Tampering, Repudiation · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. A scheme client changes the payload after the guard approved it, or a seller reports a settlement that did not happen, reports another payment’s transaction hash, or settles through its own contract so the transaction does not look like a direct settlement. What Stellar makes different. The host records every authorization nonce it consumes in a temporary ledger entry keyed by the payer and the nonce, and only the entry the payer signed can consume it. A transaction used a payment’s authorization exactly when it carries the payer’s entry with that payment’s nonce and its meta creates that nonce entry, whatever its call path: Soroban matches an entry below the top-level call, so a wrapper, router or batch contract can settle it too, and one transaction can consume several nonces. A transfer event alone says nothing about which authorization moved the money. Preconditions. A malicious scheme client or seller. Impact. Paying for something else, believing a payment failed or succeeded wrongly, or a session budget that under-counts what moved. Mitigations
  • The guard compares the payload with what it signed. (packages/sdk/src/policy/guard.ts, checkGuardedPayload)
  • A payment is proven only by its own nonce: a transaction that consumed it settles the reservation whatever its shape, for the exact price, or for upto the amount the signed settlement contract recorded for the signed terms (the whole cap when that cannot be read). A consumed authorization is never released. (packages/sdk/src/policy/proof.ts, reservationSettlement)
  • A reported hash is only a claim against the payment it was reported for and counts as spent; it never removes a transaction from another payment’s proof. Budget returns only when the authorization has expired and no transaction in its validity range consumed it. (packages/sdk/src/policy/reconcile.ts, reconcile)
  • An expired payment the RPC can no longer prove either way counts as spent at its whole amount and is no longer reconciled. (packages/sdk/src/sqlite/ledger.ts, markUnprovable)
Tests Residual risk. A payment that is not reconciled before the RPC’s retention ends counts as spent in full, even if it never moved money.

M7. Another local process drives the MCP server over HTTP

Mitigated · Spoofing, Elevation of privilege · boundary TB6 (Agent, MCP server and paid URLs) · asset: Agent budgets Threat. A web page (DNS rebinding) or another local process reaches the MCP server’s HTTP transport and spends through its tools. What Stellar makes different. The MCP server holds the agent’s Stellar signing key. Any caller that reaches its tools can make it sign Soroban authorization entries, bounded only by its per-call and session caps (M1) and its network, asset and recipient allow lists (M2); keeping other callers out is what keeps that key the agent’s. Preconditions. The HTTP transport enabled. Impact. Spending by an unauthorised caller, within the caps. Mitigations
  • The HTTP transport listens on loopback, requires a bearer token and refuses foreign Host and Origin headers. (packages/mcp/src/http.ts, hostHeaderValidationResponse)
Tests Residual risk. None; stdio is the default transport.

The upto contract

With upto the payer signs a cap, and the settlement authority named in the terms chooses the amount at settlement. The contract x402-upto-stellar has no admin, no storage and no upgrade path; the payer’s signature covers the terms and an approve of the cap to the contract, and the authority’s authorization covers the amount.

U1. Settling more than the cap

Mitigated · Tampering, Elevation of privilege · boundary TB7 (Contracts) · asset: Payer funds Threat. The settlement authority, a bug or a hostile seller settles an amount above the cap the payer signed. What Stellar makes different. The payer’s signature covers approve(payer, contract, max_amount, expiration_ledger); the token would refuse a transfer_from above the allowance, and the contract refuses first. Preconditions. Control of the settle arguments. Impact. None: refused. Mitigations Tests Conformance runs. live-matrix case upto-over-cap, rejection-audit case upto-settle-over-cap. Residual risk. None.

U2. Changing the recipient, token, expiry, nonce or tree

Mitigated · Tampering · boundary TB7 (Contracts) · asset: Payer funds Threat. The terms or the authorization tree are changed after signing: another recipient, token, facilitator, expiration, nonce or an extra sub-invocation. What Stellar makes different. Every term is an argument of the signed settle(terms) root invocation and of its approve child; the host checks the signature over the whole tree, and the network id and nonce are in the preimage. Preconditions. A copy of the payload. Impact. None: signature verification fails. Mitigations
  • The facilitator checks every bound field and the exact tree shape before simulating; the host rejects any change the facilitator missed. (packages/upto/src/authorization.ts, inspectUptoAuthorization)
Tests Conformance runs. rejection-audit case upto-wrong-recipient, rejection-audit case upto-wrong-asset, rejection-audit case upto-authorization-tree, rejection-audit case upto-forged-terms. Residual risk. None.

U3. Settling one authorization twice

Mitigated · Tampering · boundary TB7 (Contracts) · asset: Payer funds Threat. The same upto authorization is settled twice, for the same or different amounts. What Stellar makes different. The payer’s entry nonce is consumed by the host on first use and kept until the signature expires; a failed settlement rolls the nonce back. Preconditions. A copy of the payload and the authority. Impact. None: the second use fails with Error(Auth, ExistingValue); Rail402 answers repeats from its ledger. Mitigations Tests Conformance runs. live-matrix case upto-repeat-different-amount, rejection-audit case upto-replay, live-matrix case upto-concurrent-same-authorization. Residual risk. None.

U4. Settling with an authority the payer did not name

Mitigated · Spoofing, Elevation of privilege · boundary TB7 (Contracts) · asset: Payer funds Threat. A facilitator, or anyone, settles an upto payment that named another settlement authority. What Stellar makes different. terms.facilitator is part of the signed terms and must authorize the settle call itself. Preconditions. A copy of the payload. Impact. None: refused. Mitigations
  • Requirements whose facilitatorAddress is not this facilitator’s authority are refused; the contract requires terms.facilitator’s authorization. (packages/upto/src/errors.ts, invalid_upto_stellar_requirements_facilitator_mismatch)
Tests Conformance runs. rejection-audit case upto-terms-authority, rejection-audit case upto-authority-not-ours. Residual risk. None.

U5. The allowance left by a partial settlement

Mitigated · Elevation of privilege · boundary TB7 (Contracts) · asset: Payer funds Threat. After a partial settlement, the remaining allowance (cap minus amount) is spent by someone else. What Stellar makes different. The signed approve grants the settlement contract an allowance of the full cap until the expiration ledger; transfer_from spends only the settled amount. Preconditions. A partial upto settlement. Impact. None: only the contract can spend the allowance, and it spends only inside a settlement. Mitigations
  • The allowance’s spender is the settlement contract; it expires with the terms and the next settlement’s approve overwrites it. (contracts/stellar/upto/src/lib.rs, transfer_from)
Tests Residual risk. Wallets show an allowance to the settlement contract until the expiration ledger.

U6. Zero settlements paid by the sponsor

Accepted risk · Denial of service · boundary TB2 (Seller and facilitator) · asset: Sponsor XLM Threat. Anyone settles many upto payments for zero; each on-chain zero settlement costs the sponsor a fee and moves nothing. A zero needs no token balance or trustline, so the caller needs only an account that can sign. What Stellar makes different. An on-chain zero consumes the nonce (so the authorization cannot be reused) but costs a Soroban transaction. Preconditions. Sellers or buyers that trigger zero settlements. Impact. Sponsor fees for payments that earn nothing. Mitigations
  • UPTO_ZERO_SETTLEMENT=offchain records a zero as a terminal success without a transaction, in the durable settlement ledger (STORE=postgres is required for it). Pubnet defaults to offchain; testnet stays on-chain for a public on-chain record. (apps/rail402/src/config.ts, zeroSettlement)
  • Pubnet refuses to start without an explicit hourly sponsor budget, which bounds on-chain zeros with every other settlement. (apps/rail402/src/config.ts, PUBNET_MAX_SPONSOR_SPEND_XLM_PER_HOUR)
  • Zero settlements are never cataloged, on-chain or off-chain. Content planted with a settlement of one base unit is not published either: listings with an origin publish only what the origin answers (P1, P9). (packages/bazaar/src/codes.ts, bazaar_settlement_zero)
Tests Conformance runs. live-matrix case upto-zero, live-bazaar case upto-offchain-zero. Residual risk. On the keyless testnet service, zeros settle on-chain and each costs one fee, bounded by the per-client rate limit (IPv6 clients are grouped by /64) and, where configured, the hourly budget. A client with many addresses passes the rate limit, so the budget is the real bound, after which every settlement is refused for the rest of the hour. On pubnet zeros settle off-chain by default and all spend is bounded by the required hourly budget.

U7. The settlement contract archived

Mitigated · Denial of service · boundary TB7 (Contracts) · asset: Availability Threat. The upto contract’s instance or code entry reaches the end of its TTL and is archived, so every upto payment fails. What Stellar makes different. Persistent entries are archived at liveUntilLedgerSeq; any invocation needs them restored. The contract keeps no storage of its own, so only the instance and code entries pay rent. Preconditions. Nobody extends the entries for months. Impact. upto payments unavailable; restoring inside a payment would bill the buyer’s sponsor (S7). Mitigations Tests Residual risk. None while the keeper runs; extension and restoration are permissionless, so anyone can also keep the contract live.

U8. A different or changed settlement contract

Mitigated · Tampering, Elevation of privilege · boundary TB7 (Contracts) · asset: Payer funds Threat. Requirements name a hostile contract as the settlement contract, or the canonical contract’s code is replaced. What Stellar makes different. Contract addresses derive from the deployer and a salt; the deployed WASM hash is readable from the instance. A contract without an admin or upgrade function cannot change its code. Preconditions. A seller or configuration pointing at another contract. Impact. A contract that spends the approved allowance differently. Mitigations
  • The contract has no admin, no storage and no upgrade path. (contracts/stellar/upto/src/lib.rs, cannot be upgraded)
  • Payments naming another contract than the network’s configured one are refused; the service refuses to start when the configured contract runs other code. (packages/upto/src/errors.ts, invalid_upto_stellar_payload_wrong_contract)
Tests Residual risk. An operator can configure a contract of their own (UPTO_TESTNET_DEPLOYMENT); buyers see it in /supported.

U9. Token differences between SAC and SEP-41 contracts

Mitigated · Tampering · boundary TB7 (Contracts) · asset: Payer funds Threat. A token whose events or allowance semantics differ from a Stellar Asset Contract is mis-read, so a wrong transfer passes the checks or a right one is refused. What Stellar makes different. Stellar Asset Contracts publish [name, from, to, asset] topics; OpenZeppelin SEP-41 tokens publish [name, from, to] with a bare amount, and their approve data is a map with live_until_ledger. Decimals are read from the token. Preconditions. A custom SEP-41 asset. Impact. False acceptance or refusal of upto settlements. Mitigations
  • Both event shapes are accepted and checked field by field; exactly one transfer of the amount from the payer to payTo, and no balance event involving a facilitator account. (packages/upto/src/facilitator/simulation.ts, checkSimulationEvents)
Tests Conformance runs. live-bazaar case second-asset. Residual risk. Tokens with fees on transfer or rebasing balances are not supported; their settlements fail the event check.

U10. The settlement authority’s key is compromised

Accepted risk · Elevation of privilege · boundary TB4 (Facilitator and its state) · asset: Payer funds Threat. The authority’s key, which is derived from the sponsor secret, leaks. What Stellar makes different. The authority chooses the amount of any upto payment that names it, up to each payment’s cap, within the payment’s validity window; it holds no funds and cannot change recipient or token. Preconditions. Leak of the sponsor secret. Impact. Payloads the attacker obtains could be settled for up to their caps, to their named recipients, until they expire; the sponsor’s XLM is at risk. Mitigations
  • The authority is derived per network with HKDF from the sponsor secret, distinct from the sponsor and the channels, and holds a zero balance. (packages/facilitator/src/authority.ts, deriveAuthorityKeypair)
  • Rotation with the sponsor: payments naming the old authority are refused by the new one and expire within the maximum timeout (300 s by default). (docs/operations.md, Rotating the sponsor key)
Tests Residual risk. Within the maximum timeout after a leak, captured payloads can be charged up to their caps. Keep caps near expected prices and timeouts short.

U11. A served buyer defaults before settlement

Accepted risk · Repudiation · boundary TB2 (Seller and facilitator) · asset: Seller revenue Threat. An upto seller serves first and settles afterwards. Between /verify and the seller’s /settle the buyer moves its funds, uses its authorization nonce in another transaction, or settles the payload itself for zero, and the seller’s settlement fails for work already done. What Stellar makes different. /verify simulates the settlement for the full cap, so the signature, balance and trustline hold at that moment, but a Soroban authorization is not an escrow: nothing is locked until the settlement transaction applies. Preconditions. A buyer willing to default, and a seller that releases output before its own settlement succeeds. Impact. One request served unpaid. A streaming or metered seller cannot withhold what it already sent. Mitigations
  • Short maxTimeoutSeconds, settling immediately after serving, withholding buffered output until settlement succeeds (the stock Express middleware does), and refusing payers whose settlements fail. (docs/facilitator/upto.mdx, Payment is not guaranteed after verification)
Tests. None: this is a documented limit, not a behaviour Rail402 implements. Residual risk. Accepted: upto is post-paid by design; the loss is bounded by one request’s cap.

exact payments

The exact scheme is verified by the upstream @x402/stellar verifier, which Rail402 wraps with its own checks before any RPC work and with its settlement engine after.

E1. Tampering with amount, recipient or asset

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Payer funds Threat. A seller or intermediary changes the amount, payTo or asset of an exact payment after the payer signed it. What Stellar makes different. The payer signs transfer(from, to, amount) on the asset contract; any change alters the invocation the host checks the signature against. Preconditions. A copy of the payload. Impact. None: refused at verify, or fails signature verification. Mitigations
  • The transfer must match the requirements; the payer’s entry must authorize exactly the operation’s invocation. (packages/facilitator/src/preflight.ts, invalid_exact_stellar_payload_auth_invocation_mismatch)
Tests Conformance runs. live-matrix case tamper-amount, live-matrix case tamper-recipient, rejection-audit case tampered-amount. Residual risk. None.

E2. The facilitator made a party to the payment

Mitigated · Elevation of privilege · boundary TB2 (Seller and facilitator) · asset: Sponsor XLM Threat. A payload names a facilitator account (sponsor, channel or upto authority) as payer, transaction or operation source, or co-signer, so the facilitator’s signature would move its own funds. What Stellar makes different. The facilitator signs the envelope and the fee bump; if one of its accounts were the source of a transfer or the subject of an auth entry, its signature would authorize it. Preconditions. Knowledge of the facilitator’s public addresses (they are in /supported). Impact. Loss of the facilitator’s funds; custody. Mitigations Tests Conformance runs. rejection-audit case facilitator-as-cosigner, rejection-audit case facilitator-as-payer, rejection-audit case facilitator-as-source. Residual risk. None.

E3. Extra invocations riding on the payment

Mitigated · Tampering, Elevation of privilege · boundary TB2 (Seller and facilitator) · asset: Payer funds Threat. The authorization tree carries sub-invocations, or the transaction carries more operations or preconditions, so that settling the payment does more than one transfer. What Stellar makes different. An authorization entry authorizes a tree of calls; a transaction may carry preconditions and several operations. Preconditions. A crafted payload. Impact. Side effects signed by the payer and paid for by the sponsor. Mitigations
  • Exactly one invokeHostFunction transfer, no sub-invocations, no unsupported preconditions, bounded size. (packages/stellar/src/payload.ts, inspectExactTransaction)
Tests Conformance runs. rejection-audit case sub-invocations, rejection-audit case multiple-operations, rejection-audit case fee-bump-envelope. Residual risk. None.

E4. An authorization valid for too long

Mitigated · Tampering · boundary TB2 (Seller and facilitator) · asset: Payer funds Threat. A payload signed with a far-future expiration stays spendable long after the purchase. What Stellar makes different. The expiration ledger is chosen by the signer; x402 derives it from maxTimeoutSeconds as ceil(T / ledger seconds) past the latest ledger. Preconditions. A client that signs long-lived authorizations. Impact. A payment that can be settled much later. Mitigations Tests Conformance runs. rejection-audit case expires-too-far, rejection-audit case timeout-out-of-range, live-matrix case expiry-window. Residual risk. None.

E5. Misreading a custom token’s failures and decimals

Mitigated · Tampering · boundary TB3 (Facilitator and Stellar) · asset: Payer funds Threat. A token whose error numbers or decimals differ from a Stellar Asset Contract is priced or explained as if it were one. What Stellar makes different. Classic assets are always 7 decimals through their SAC; SEP-41 contract tokens choose their decimals and their own error numbers. Preconditions. A custom SEP-41 asset configured. Impact. Wrong prices by orders of magnitude, or wrong refusal reasons. Mitigations Tests Residual risk. None.

Operations

Crashes, outages, sequence numbers, retention windows and keys: what happens to a payment when the service itself fails.

O1. A crash after submission

Mitigated · Denial of service, Repudiation · boundary TB4 (Facilitator and its state) · asset: Payer funds Threat. The process dies after signing or broadcasting a settlement and before recording its outcome; a restart submits a replacement and the payer is charged twice, or the seller is never told. What Stellar makes different. A signed envelope stays valid until its time bound; the network may include it after the process died. Preconditions. A crash, deploy or kill at the wrong moment. Impact. Double charge (without protection) or a lost success. Mitigations Tests Conformance runs. fault-recovery case k0-exact, fault-recovery case k1-exact, fault-recovery case k3-exact, fault-recovery case k0-upto, fault-recovery case k1-upto. Residual risk. A settlement whose ledgers the RPC no longer retains stays unresolvable and is reported (O4).

O2. Channel sequence collisions

Mitigated · Denial of service · boundary TB4 (Facilitator and its state) · asset: Availability Threat. Two settlements use the same channel account at once, or something else uses a channel’s key, and transactions fail with a bad sequence number. What Stellar makes different. An account’s transactions are ordered by sequence number; one in-flight transaction per source is the rule. Preconditions. Concurrency, several replicas, or external use of a channel key. Impact. Failed settlements and wasted fees. Mitigations Tests Conformance runs. concurrency case distinct-sequences. Residual risk. Reusing the sponsor seed in another deployment derives the same channels; do not.

O3. The RPC or the settlement store is unavailable

Mitigated · Denial of service · boundary TB3 (Facilitator and Stellar) · asset: Availability Threat. The Stellar RPC or Postgres stops answering while payments arrive. What Stellar makes different. Without the RPC nothing can be simulated, submitted or confirmed. Preconditions. An outage. Impact. Payments refused, or worse, settled without a record. Mitigations Tests Residual risk. None beyond downtime.

O4. Settlements past the RPC’s retention

Mitigated · Repudiation · boundary TB3 (Facilitator and Stellar) · asset: Seller revenue Threat. A recorded settlement is examined after the RPC no longer keeps the ledgers that could prove it included or absent. What Stellar makes different. An RPC keeps only a limited window of recent ledgers and transactions; older ones cannot be read from it. Preconditions. A long outage or a restored backup. Impact. A payment wrongly declared expired while it did settle. Mitigations Tests Residual risk. An operator resolves them from an archive or an indexer.

O5. All channels busy

Mitigated · Denial of service · boundary TB4 (Facilitator and its state) · asset: Availability Threat. More settlements arrive than there are channel accounts. What Stellar makes different. Each channel carries one in-flight transaction. Preconditions. A burst of payments. Impact. Slow or refused settlements. Mitigations Tests Residual risk. Throughput is bounded by the channel count; raise CHANNEL_COUNT.

O6. The sponsor secret is compromised

Accepted risk · Elevation of privilege, Information disclosure · boundary TB4 (Facilitator and its state) · asset: Secrets Threat. The sponsor seed leaks from the platform, a log or a backup. What Stellar makes different. The sponsor seed derives the channels and the upto authority; none of them can move a payer’s funds. Preconditions. A leak. Impact. Loss of the sponsor’s XLM; U10 for upto payloads in flight. Mitigations
  • The seed is never logged or echoed; configuration errors name the variable, not the value. (apps/rail402/src/logger.ts, REDACTED_PATHS)
  • Documented rotation: retire channels, move funds, provision with the new seed. (docs/operations.md, Rotating the sponsor key)
Tests Residual risk. Keep only a working balance on the sponsor.

O7. Degraded search indexing

Mitigated · Denial of service · boundary TB2 (Seller and facilitator) · asset: Catalog and search integrity Threat. The embedding model fails or is slow, or its files are tampered with. What Stellar makes different. None. Preconditions. A model failure or a changed model file. Impact. Wrong or missing search results. Mitigations Tests Residual risk. None.

The Playground

A testnet key in a browser page, and a server that relays the page’s seller requests: what a visitor, another site or a seller could do with either.

PG1. The relay used to reach private networks or as an open proxy

Mitigated · Spoofing, Information disclosure, Denial of service · boundary TB8 (Playground page and server) · asset: Operator infrastructure Threat. A visitor, or a script calling the Playground’s API, asks the relay to fetch a loopback, private or cloud-metadata address, a name that resolves to one, or a seller that redirects to one; or uses the relay to send volume to third parties from the Playground’s address. What Stellar makes different. Nothing Stellar-specific: the relay exists because Rail402 and most x402 sellers answer no CORS headers, so a page cannot call them itself. A paid request through it carries a PAYMENT-SIGNATURE the visitor’s own key made. Preconditions. Network access to the Playground server. Impact. Without protection: requests into the server’s network, or the Playground’s address used for abuse. Mitigations
  • The relay sends with safe-fetch under a fixed policy: https on port 443 to public addresses only, DNS answers checked and pinned, redirects refused except same-origin hops for unpaid probes, request and answer size limits. (apps/playground/src/relay/egress.ts, RelayEgress)
  • By host name, it never contacts the Playground itself (its public host and the platform’s generated domain), the facilitator, the Bazaar or an operator-denied host. (apps/playground/src/relay/egress.ts, playground_relay_loop)
  • Per-client, global, per-host, in-flight and daily byte limits, with Retry-After, and a switch to turn the relay off. (apps/playground/src/limits.ts, DailyBudget)
  • Global limits are fair-shared: one network (an IPv4 address or an IPv6 /48) holds at most an eighth of a pool, or its per-client limit when that is larger; clients are keyed by IPv4 address or IPv6 /56. (apps/playground/src/limits.ts, SharedRate)
  • Only a payment that decodes as an x402 payment counts against the paid limits, whether it travels in PAYMENT-SIGNATURE or in an MCP tool call’s _meta; anything else is refused before a token is taken. (apps/playground/src/payment-header.ts, mcpPaymentOf)
  • The demo seller has a paid pool of its own, a paid request that reached no seller gives the shared pool its token back, and the lab sellers return it when the facilitator was never asked, so payments that only have the right shape cannot use up the pools the demo flows need. (apps/playground/src/relay/relay.ts, trustedPaidGlobal)
  • Unpaid requests wait no longer than the SDK’s probe timeouts; bodies pass only as JSON, text or octet-stream; slots of an abandoned upload or an unread answer are released; connections to hosts other than the demo seller are closed after each answer. (apps/playground/src/relay/relay.ts, timeoutsFor)
  • Unpaid requests and answer bytes are also limited per network, bytes for all clients per day, requests in flight per destination host, and each network gets at most its share of a host’s budget. (apps/playground/src/limits.ts, KeyedSharedRate)
Tests Conformance runs. live-playground case ssrf-literal, live-playground case ssrf-metadata, live-playground case redirect. Residual risk. For a client that is not a browser, the relay is an open https egress to public hosts at the Playground’s address, bounded only by the limits; IP-keyed limits cannot stop a client with many addresses or networks. The payment check stops only malformed payments: a payment that merely has the right shape is counted as paid, so enough addresses can still use up a shared paid pool for a minute at a time. Loop and deny lists match host names: another name of a denied host is caught only when the operator lists it. Its user agent names the Playground, and the operator can deny hosts or switch the relay off.

PG2. Another site drives the relay from a visitor’s browser

Mitigated · Spoofing, Information disclosure · boundary TB8 (Playground page and server) · asset: Operator infrastructure Threat. A page on another origin makes a visitor’s browser send requests through the Playground’s relay, seller check or proxies, or reads their answers. What Stellar makes different. The relay holds no key and no session, so a forged request carries no authority the attacker lacks; a payment needs a signature from the key in the Playground’s own page. Preconditions. A visitor with the Playground open, or not, visits the attacker’s page. Impact. Without protection: the Playground’s relay used through visitors’ browsers. Mitigations
  • The relay needs its own request headers, which a cross-site form cannot set; a browser’s Sec-Fetch-Site must be same-origin and its Origin the Playground’s public origin. (apps/playground/src/security.ts, sameOrigin)
  • The relay answers no CORS headers, so no other origin can read an answer. (apps/playground/src/shared/relay-protocol.ts, x-relay-target)
  • The Bazaar and /supported proxies refuse another site’s requests the same way, so no page can spend their shared upstream budget through visitors’ browsers. (apps/playground/src/proxy/bazaar.ts, proxyOrigin)
Tests Residual risk. Clients that are not browsers send neither header and pass. They get nothing a browser visitor lacks, but they can use the relay as an https egress at the Playground’s address, bounded only by the limits (PG1).

PG3. Script injected into the page uses the testnet key

Mitigated · Tampering, Elevation of privilege · boundary TB8 (Playground page and server) · asset: Secrets Threat. Seller-supplied text (a description, an error, a response body) or a third-party script runs in the page and signs with, or reads, the wallet’s key. What Stellar makes different. The key signs Soroban authorization entries through the SDK’s signing guard; a script in the page could ask it to sign within the visitor’s caps. Preconditions. A seller or listing the visitor’s page displays. Impact. Without protection: testnet funds spent within the caps, or the testnet key read out. Mitigations
  • A content security policy without inline or third-party script, styles or frames from the Playground’s origin only, and connections only to the configured Stellar endpoints. (apps/playground/src/security.ts, contentSecurityPolicy)
  • Seller text is rendered as text, and an untrusted URL becomes a link only when it is https and parses; no view renders raw markup. (apps/playground/web/src/ui/links.tsx, safeHttpsUrl)
  • The stored seed is encrypted under a key WebCrypto generated as non-extractable; only the wallet modules touch the key. (apps/playground/web/src/wallet/keystore.ts, non-extractable)
Tests Conformance runs. live-playground case secret-scan. Residual risk. A script that did run in the page would not be bound by the caps: it could open the page’s IndexedDB, decrypt the stored seed with the stored key (non-extractable protects the key, not what it decrypts) and send the seed out. The CSP and the absence of markup sinks make that unlikely. The key is meant for testnet only, and the import field says never to paste a key that holds real funds.

PG4. The seller check used to scan ports or private hosts

Mitigated · Information disclosure, Denial of service · boundary TB8 (Playground page and server) · asset: Operator infrastructure Threat. A visitor submits private addresses, other ports or many URLs to the seller check to map the server’s network or to load third parties. What Stellar makes different. The check runs the SDK’s seller checks: the URL’s 402, its discovery metadata, whether payTo can receive the asset, and the seller’s stellar.toml and home domain. Preconditions. Network access to the Playground server. Impact. Without protection: a port scanner inside the server’s network. Mitigations Tests Residual risk. The check reaches public https hosts on port 443 at its rate.

PG5. The lab sellers take real payments or pollute the catalog

Mitigated · Tampering, Denial of service · boundary TB8 (Playground page and server) · asset: Payer funds Threat. The Playground’s misbehaving sellers (the offers, the greedy upto route) take a payment that sticks, or every visitor’s sandbox sale adds a listing to the public Bazaar. What Stellar makes different. An upto authorization caps what any settlement can move; Rail402 refuses a settlement above it, and the settlement contract enforces the cap on chain too. Preconditions. A visitor running the lab. Impact. Without protection: visitors charged more than they authorized, or a catalog flooded with sample listings. Mitigations
  • The offers answer a 402 only and never verify or settle. (apps/playground/src/lab/offers.ts, offerHandler)
  • The greedy route asks to settle twice its cap, which Rail402’s upto rules refuse; it declares no discovery metadata. (packages/upto/src/facilitator/rules.ts, invalid_upto_stellar_payload_settlement_exceeds_amount)
  • The sandbox route answers the same 402 for every city and visitor, so it is one listing paid to one operator account. (apps/playground/src/lab/sell.ts, declareSellRoute)
  • Its discovery metadata always carries the declared example city, never the one a request names, and a path that is not a city name gets no 402, so no payer writes text into the shared listing. (apps/playground/src/lab/sell.ts, sellBazaarExtension)
Tests Conformance runs. live-playground case overcharge, live-playground case overcharge-release, live-playground case refusal-network, live-playground case refusal-asset, live-playground case refusal-amount, live-playground case sell-listed. Residual risk. None for funds. The sandbox listing is public and permanent, as one listing; its resource URL names the city of the payment that created it.

PG6. The stored wallet read or overspent

Mitigated · Information disclosure, Elevation of privilege · boundary TB8 (Playground page and server) · asset: Agent budgets Threat. The wallet’s stored record is swapped or tampered with, or two tabs spend one session budget at the same time. What Stellar makes different. The budget ledger reserves each payment before it is signed; two writers of one stored ledger could each see room the other already used. Preconditions. Access to the browser profile, or two open tabs. Impact. Without protection: a different key loaded, or the session budget exceeded. Mitigations Tests Conformance runs. live-playground case refusal-session. Residual risk. The record is encrypted under a key kept in the same browser profile, not under a passphrase: anyone using that profile can use the wallet through the page. It is a testnet key with testnet funds.

PG7. Payments or the key leak into logs, traces or storage

Mitigated · Information disclosure · boundary TB8 (Playground page and server) · asset: Secrets Threat. A PAYMENT-SIGNATURE, a signed authorization or the key ends up in the server’s logs or traces, an error, the page’s storage, a run report or a request to a third party. What Stellar makes different. A signed authorization is spendable by whoever holds it until it expires. Preconditions. Any run. Impact. Without protection: an authorization replayable by whoever reads the channel, or the key exposed. Mitigations Tests Conformance runs. live-playground case secret-scan. Residual risk. The relay holds a payment header in memory for the one request that carries it, as any seller does.

Out of scope

  • Whether a seller delivers what it sells, or charges a fair upto amount within the cap the buyer signed.
  • The security of buyers’ wallets and of the machines agents run on.
  • The integrity of the Stellar RPC provider beyond what simulation and the ledger prove; the RPC URL is operator configuration.
  • Attacks on the hosted testnet instance’s infrastructure; see SECURITY.md for how to report a vulnerability.