Skip to main content
Scope: the STELLARSIGHT facilitator, the Bazaar catalog and the agent-facing surfaces, as deployed on stellar:testnet today. Written before mainnet on purpose — a threat model produced after launch documents decisions instead of informing them. This is v0.1. It states what is defended today, with the test that proves it, and what is not, without dressing the gaps up as future work that is somehow already handled. Tranche 2 turns the rest of it into a running system — the fee-payer rows (T6) already are — with alerts firing against live thresholds (MONITORING.md).

Assets worth attacking

Trust boundaries

  1. Seller → facilitator. Discovery metadata is attacker-controlled. Clients echo the resource block back inside the payment payload, so every field crossing this boundary is hostile input.
  2. Buyer → facilitator. The payment payload is attacker-controlled; cryptographic validity is delegated to @x402/stellar, never reimplemented here.
  3. Facilitator → Stellar. The only party that can move funds is the buyer, via their own signature. The facilitator is non-custodial: it sponsors fees and submits.
  4. Catalog → agent. Everything the catalog returns will be read by an LLM-driven agent. Text in a listing is untrusted content, not instructions.

Threats, controls, and the test that proves each

Note on T7: why there is now a facilitator-side nonce memory

An earlier version of this document said there was deliberately no facilitator-side replay cache “to be poisoned or bypassed”. That reasoning was right about a cache used as a control and is the reason this one is not used as one. What changed is the reporting, not the enforcement. @x402/stellar reads the real Soroban simulation error, prints it to the operator’s stdout, and returns invalid_exact_stellar_payload_simulation_failed to the caller — so a replay, an underfunded account and an expired signature were indistinguishable to an agent. The facilitator now records the (address, nonce) pairs it has settled, which is the same key Soroban replay-protects on, and uses it to name a replay it recognises. The original two objections do not apply to a memory used this way:
  • Bypass is free and costs nothing. Evading the memory means reaching the chain, which refuses the replay anyway. A miss downgrades the error message; it never enables a double spend.
  • Poisoning would require the victim’s signature. Entries are written only by this facilitator’s own successful settlements, keyed by the payer address inside the signed authorization entry. To pre-block someone’s nonce an attacker would have to settle a payment signed by them, with the exact nonce they will use next.
The remaining failure mode is a false positive from a corrupted store, so every unknown or unparseable value is treated as not seen (apps/facilitator/src/settled-nonces.mjs), as is an unreachable store. The layer fails open in every direction: worst case the caller gets the same opaque code it got before.

Note on T13: why ordering is not an attack surface here

Front-running is a real threat class in payments, and the honest reason it does not bite here is structural rather than defensive — worth writing down, because “we are not vulnerable” is a claim a reviewer should be able to check rather than accept. An attacker who observes a payment payload in flight holds a signed authorization entry that names a recipient, an asset, an amount, a nonce and an expiry ledger. Submitting it first does not help: it settles the payment the buyer already authorized, to the seller the buyer already chose, for the amount the buyer already agreed — at the attacker’s own expense if they sponsor the fee. Altering any bound field invalidates the signature, which is the rejection verify-rejections audits by name. The nonce is consumed on chain (T7), so it cannot be replayed behind the original either. What normally makes ordering valuable is absent by construction: there is no auction, no slippage, no price that depends on the order of execution, and no pool position to take ahead of someone. A transfer at a price fixed in the 402 challenge pays the same amount to the same account regardless of when in the ledger it lands. What this does not cover, stated so the row is not read as broader than it is: a malicious or failing facilitator can delay a settlement or decline to submit it at all, and it can observe who is paying whom. That is censorship and metadata exposure, not front-running, and the answer to it is the same one the whole design rests on — the facilitator is non-custodial and replaceable, so a buyer who is being stalled can settle the same payment through a different one. Both are named again under residual risk when this becomes a mainnet question.

Non-custodial by construction

The facilitator holds no user funds and has no deposit or withdrawal path. Every settlement is a direct SEP-41 transfer from the buyer’s account to the seller’s, authorized by the buyer’s own signature over the full invocation. A compromised facilitator can refuse to serve, and can waste its own sponsored fees. It cannot redirect a payment, alter an amount, or move funds it was not authorized to move.

Residual risk, stated plainly

  • Small team. Three engineers, with the scopes split and owned: the facilitator and discovery core; the Soroban settlement contract and the seller-facing SDK and CLI; the hosted agent surface and the operational hardening around it. The commit history to date is concentrated in one author because the stack was built before any funding — git shortlog says so and pretending otherwise would be silly — but that is a fact about when the work happened, not about how many people are on it. The mitigations hold either way: Apache-2.0 end to end so nothing here is trapped behind us, public CI anyone can run, and the Tranche 3 handoff deliverable (named maintainer, runbooks).
  • No external audit yet. Scheduled through the SCF Audit Bank in Tranche 3; the audit fee is excluded from the budget per the rules.
  • Testnet only. No real funds are at risk today. Mainnet is gated behind the audit remediation and the monitoring in MONITORING.md being live.
  • Single shared write token. Adequate for a demo catalog, inadequate for a public index with third-party sellers. Tranche 1 replaces it with per-seller payTo binding.

Review

Revisit at each tranche boundary, and whenever the x402 spec moves (it went 2.21 → 2.22 during development). Findings from the Tranche 3 security review land here with their remediation, tracked as public issues.