| 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 | 66 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 | PARTIAL. Control characters and RTL overrides are stripped and lengths capped, which limits the payload. Nothing marks listing text as untrusted at the MCP boundary | Gap — noted for Tranche 2 alongside the hosted MCP endpoint |
| 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; no alerting yet — Tranche 2 |
| T7 | Replay of a signed authorization | Soroban host nonce + signatureExpirationLedger. Enforced on-chain, not by us: there is deliberately no facilitator-side replay cache to be poisoned or bypassed | apps/agent/src/replay-guard.test.mjs (live-path test, skipped without a running facilitator) |
| 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 (46 checks) runs in CI on every push and on a nightly schedule |