Security and trust model#
What a compromised Turnpike could and could not do#
The useful question is not “is the facilitator trustworthy” but “what is the worst it can do if it isn’t.”
It cannot redirect a payment. The recipient is bound inside the payer’s signed Soroban authorization entry. Changing it invalidates the signature.
It cannot inflate a charge. Under exact, the amount is bound in the signed material. Under upto, the cap is bound and the contract enforces actual ≤ cap.
It cannot settle twice. Under exact, a Soroban authorization entry is single-use: the second submission of a settled payload is rejected by the network, not by facilitator bookkeeping, so the guarantee already survives my compromise today. Under upto, nonce-once would be enforced on-chain by the contract for the same reason.
It cannot take custody. Turnpike never holds buyer or seller funds. Its own accounts hold only XLM for sponsoring network fees.
It can fail to settle, settle late, or settle for less than the authorised cap. Those are the residual risks, and they are the ones an integrator should reason about.
This shape is deliberate. Every guarantee above is a property of the ledger or of the payer’s signature, not a promise from me.
Key handling#
Turnpike holds two categories of key, separated by role:
Sponsor key — pays network fees. Holds XLM only. Compromise means an attacker can drain the fee balance; it does not give access to payment flows.
Channel account keys — used as transaction sources for sequencing. Hold only the minimum reserve. (Designed; today a single account serves as both sponsor and transaction source.)
Turnpike never holds payer keys. The payer signs their authorization entry client-side. No signing material reaches the facilitator.
All keys are injected through environment variables and never committed. .env is gitignored; .env.example carries placeholder values only. This is enforced in review rather than assumed: a testnet key in a public repository reads as carelessness even when the funds are worthless, and the habit that permits it is the one that later leaks a mainnet key.
Rotation procedures for sponsor and channel keys are a Tranche 3 deliverable alongside the operational runbook.
Decentralization#
Turnpike is infrastructure, and honest positioning matters more here than optimistic claims.
What is decentralized. Settlement is on Stellar; Turnpike cannot alter or reverse it. Payment authority rests with the payer’s signature. Catalog ownership derives from on-chain settlement rather than from an operator’s decision. The upto contract’s guarantees are enforced by the ledger.
What is not. A running Turnpike instance is an operated service. It can be unavailable. It can decline to settle. It chooses what appears in its own catalogue’s ranking.
How that is mitigated. The entire stack is Apache-2.0 and self-hostable, and the deployment path is a first-class deliverable rather than an afterthought — the demo runs from a clean clone with no pre-provisioned secrets. Sellers are not locked in: the facilitator endpoint is configuration, and switching to a different one, including one you run yourself, is a config change. Because Turnpike is conformant, a seller can point at any x402 facilitator and a buyer can use any x402 client.
The strongest anti-lock-in property is conformance itself. A facilitator that requires a custom client has captured its users; one that works with the stock client has not.
What I am not claiming. This is not a trustless system and I do not describe it as one. An on-chain resource registry would decentralize the catalogue further; it is explicitly out of scope for this grant, with reasoning in Bazaar discovery.
Privacy and data handling#
What is stored. Resource metadata — descriptions, MIME types, route templates — and the settled recipient address that establishes listing ownership. Operational state in Redis: settled-payload markers, rate-limit counters, channel leases, all TTL-bounded.
What is not stored. No payer identities beyond what is already public on the ledger. No request or response bodies. No signing material. No API keys belonging to sellers. No behavioural profiles of buyers.
Search queries are handled as transient. Any aggregate signal used for ranking — the searched-paid-not-retried loop described in Bazaar discovery — is derived at the resource level, not the payer level. I do not need to know who found a resource useful in order to know that it was useful, and the version that requires knowing is the one that becomes a surveillance asset.
Rate limiting today is per-IP, 120 requests per minute across /verify, /settle and /supported; per-payer limiting is designed, not built. IPs are used for counting and are not retained.
Everything settled is public on Stellar by design. Turnpike does not add a private index of who bought what.
Abuse resistance#
| Vector | Mitigation |
|---|---|
| Replayed payment payload | On-chain single-use authorization entries today; settled-payload markers and upto nonce-once are designed hardening |
| Forged catalog listing | Ownership bound to settled payTo; first-writer-wins; conflicts quarantined |
| Catalog flooding | Listings require a settled payment — spam costs real money |
| Path traversal via route template | Percent-decode before traversal validation |
| Malformed listing fields | Soft-dropped and reported via extension-responses header |
| Request flooding | Per-IP rate limiting today, 120 requests/minute on the paying endpoints; per-payer limiting is designed |
| Fee-balance drain | Sponsor account isolated; per-transaction fee caps |
| Degraded RPC causing false rejections | Ledger-skew retry; upstream fix proposed |
The adversarial test suite covering these is a named deliverable with its own completion criterion, not a byproduct of other work.
Audit#
The upto Soroban contract and the settlement paths are the audit surface. Audits are provided through the SDF Audit Bank at Tranche 3 and are correctly excluded from my budget.
Budgeted engineering time covers remediation, gated as a release blocker: all critical and high findings resolved or formally accepted before mainnet launch, re-verified in the codebase, with the remediation summary published in the repository.
Reporting a vulnerability#
Security issues should be reported privately rather than through public issues. The disclosure policy, response targets, scope, and reporting contact are published in SECURITY.md in the repository.