← All posts

We probed 171 third-party endpoints and 1,712 of our own handlers. Here is what broke.

November 2026

A marketplace only measures what it decided to measure. Our delivery ledger records failures. It does not record "this request returned 200 OK, held the single-threaded sidecar for five seconds, and produced 80 MB". That second category is the one that hurts, and it is invisible in every error-rate dashboard.

This is a write-up of two audits we ran on our own catalogue: one over our handlers, one over the third-party listings we carry. We are publishing the method and the numbers, including the part where our own probing was wrong.

1. Our own handlers: the failure that does not show up in error rates

We scanned every handler for allocation sites whose size comes from the caller. Three stood out, all before fixes:

A loan amortisation endpoint answered a single request with 80,763,962 bytes in 5.33 s.

A depreciation endpoint answered with 10.9 MB.

A lorem-ipsum endpoint answered with 6.6 MB.

None of these is an error. All three return 200. They are the shape that makes a service look healthy while it is being used as a memory amplifier — and on a single-threaded sidecar, one such call delays everyone else behind it.

The first version of our static guard only looked at .repeat(), Array.from and new Array. It missed for loops entirely. Adding loops to the population took the scan from 25 handlers to 81, which is how the three above were found at all. The guard was not wrong; it was answering a narrower question than we thought.

After the fixes we re-ran the scan over all 1,712 handlers and libraries, then probed 140 candidates live — extreme value into each handler's first declared input field, measuring response bytes and wall-clock time. One endpoint was flagged:

x402-perp-funding-radar   asset=1000000   code=200   bytes=2,098   t=1.938s

That one is not amplification. It is slow because it actually queries four different venues. Both numbers are honest: two kilobytes, two seconds.

Result: zero unbounded allocation sites. We should state the limits of that claim. We only used the first declared field, and we probed 140 of 174 candidates. Multi-field amplification (rows times columns, for example) was not covered.

2. Third-party listings: what 171 unpaid probes found

We carried 171 active third-party listings at the time of the audit. For each one we sent a single unauthenticated request and sorted the outcome into four buckets. An endpoint answering 402 is a correct x402 endpoint and is not counted as a defect.

Cross-host redirect

0

The catalogue names one host, the endpoint serves another. None found.

Response > 2 MB

0

None found.

No payment gate

10

Answered 2xx without payment. Partly our fault — see below.

Dead

29

Timeout, 5xx, or unreachable.

The "free" bucket is mostly a probing error

Our first pass used GET for every endpoint. An endpoint that requires POST will happily answer 200 to a GET — that is its routing behaviour, not its price. Re-probing with a payment-shaped POST moved one of the ten to 402, which means it was a correct x402 endpoint all along.

At least one really does answer everyone without a payment gate, on both methods. But the honest summary is: a 200 from a GET probe is not evidence that a service is free. It is evidence that the route exists.

The dead ones are one operator, not twenty-nine

Twenty-nine dead listings sounds like a catalogue falling apart. Grouped by operator, they are not: one operator publishes six subdomains under a single domain, all answering 503, plus one unrelated host returning 530. That is one abandoned deployment, not twenty-nine independent failures.

A per-listing view hides exactly this. If you count dead entries without grouping them by who operates them, you will overestimate how sick your catalogue is — and you will under-estimate how much one abandoned operator costs you.

3. What we changed in our method

We have not changed any third-party listing as a result of this audit. The dead ones are recorded, and delisting a batch of them is a decision we want to make slowly rather than in one sweep.

← All posts