| T1 | Path traversal in routeTemplate — a listing escapes its own route (/v1/%252e%252e/admin) | Fixed-point percent-decoding (capped at 5 passes, fail-closed on malformed %), traversal check after decode | test/catalog-integrity.test.mjs — single, double and triple encoding |
| T2 | SSRF via iconUrl — the catalog is used to probe internal networks | Host deny-list covering IP literals in decimal/hex/octal, [::1], 169.254.169.254, 0.0.0.0, userinfo tricks, and the data:/file:/javascript: schemes | 70 adversarial cases in the same suite |
| T3 | Catalog flooding — one seller drowns the index | Per-record caps (serviceName 32, 5 tags × 32, description 512), dedupe before cap, soft-drop: the hostile field is dropped and the legitimate record survives | integrity suite; the survival invariant is asserted explicitly |
| T4 | Listing hijack — a seller lists a resource they do not own, or under someone else’s payTo | PARTIAL. Cataloging is bound to a settled payment, and the seller announce path checks baseUrl against Host. There is no per-seller identity: the hosted write path is one shared bearer token | Gap — Tranche 1 deliverable 1.2 (TOFU payTo binding + ownership re-verification) |
| T5 | Prompt injection through listing text — seller metadata instructs the reading agent | Control characters and RTL overrides are stripped, lengths capped, and seller metadata is wrapped in explicit [UNTRUSTED_SELLER_CONTENT: ...] markers at the MCP boundary | test/mcp-hosted.test.mjs, scripts/verify-serverless.mjs |
| T6 | Fee-payer drain — an attacker forces sponsored fees until XLM runs out | Fee ceiling of 500,000 stroops per transaction, empirically calibrated (observed simulation ~57,000); settlement fails at /verify before money moves if simulation exceeds it | The ceiling and its derivation are in apps/facilitator/src/server.mjs; balance, burn rate and runway are read off Horizon by scripts/feepayer-runway.mjs nightly and every 6h by .github/workflows/feepayer-alert.yml, which pages on a breach (MONITORING.md) |
| T7 | Replay of a signed authorization | Soroban host nonce + signatureExpirationLedger, enforced on-chain. The facilitator additionally remembers the (address, nonce) pairs it settled — see the note below — but that memory is a naming layer, never the control | test/replay-naming.test.mjs (live path + the fail-open properties), apps/agent/src/replay-guard.test.mjs |
| T8 | Sequence-number contention as denial of service | NOT DEFENDED. One FEEPAYER means one sequence number. Measured: 4/4 payments succeed serially, 1/10 succeed at 10-way concurrency | LOAD-BASELINE.md — Tranche 1 deliverable 1.1 (channel-account pool) |
| T9 | Ranking manipulation — a seller fakes settlements to climb the results | Quality prior capped at 0.25 against relevance’s 1.00, so popularity can only break ties; seed records pinned to settlements: 0 so demo breadth can never inflate a total | Asserted by test: a 900k-settlement record loses to a zero-settlement one when the query matches the latter. Still gameable by real cheap settlements — stated in SEARCH-QUALITY.md |
| T10 | Durable-store compromise — a record is written straight into Redis, bypassing validation | Only the post-validation record is persisted, by both writers (settle path and announce path). The store is never a validation bypass | packages/index/src/store.mjs accepts what the catalog stored, not the request body |
| T11 | Store outage | Read-only degradation: the catalog serves the seeded corpus and /discovery/health reports mode, writable and the store error rather than 500-ing | test/store-transport.test.mjs; degraded path exercised in verify:api |
| T12 | Silent spec drift — the wire format stops matching the spec and nobody notices | Stock-client conformance: unmodified @x402/fetch and withBazaar() drive the real endpoints; this caught a real v1/v2 drift in our own seller | npm run verify:conformance (settled hashes published; needs a funded payer, so it runs per release); npm run verify:api (54 checks) runs in CI on every push and on a nightly schedule |
| T13 | Front-running the settlement — an observer sees a payment payload in flight and profits by submitting ahead of it, or by reordering | The buyer’s authorization entry binds recipient, asset, amount, nonce and expiry ledger, and is valid only for that exact invocation, so a payload copied out of the mempool cannot be redirected, repriced or usefully raced. There is no auction, no state-dependent price and no on-chain ordering advantage to capture in a fixed-price SEP-41 transfer | scripts/verify-rejections.mjs — “a tampered echoed price/recipient is refused before settlement”, audited against the live stack nightly; the ordering argument is in the note below |