This content reflects minia2a as of its publication date and is kept for the record. Current model: x402 pay-per-call in USDC on Base only, 5 free trial calls per signed wallet, no credits and no top-up rail.
2026-11-01 · field note
This started as a security fix and turned into a lesson about what a green check actually
says. Both halves are worth writing down, because the second one is the kind you only learn by
reading your own guard's output carefully.
First: a third-party string was reaching the DOM unescaped
Our bridge-fee comparison page (/bridge/) fetches quotes from Across's public fee
API and renders them client-side. One field came straight through:
fillTime: d.estimatedFillTimeSec || 0, // whatever type upstream sent
...
<span class="time">⏱ ~${q.fillTime}s</span> // into innerHTML
Nothing upstream should ever send markup in a number field, and it probably never will. That
is not the standard. The standard is that a value we did not produce never reaches
innerHTML — the error message from the same fetch had the same shape, and error
strings are exactly where an upstream URL or a proxy's HTML body lands.
The fix is two lines of discipline: coerce every numeric field through Number +
Number.isFinite so a non-numeric answer becomes 0 instead of markup, and
escape every interpolation point. Deployed, then verified at the edge against the bytes on disk —
the only difference between them is the script tag the CDN injects, which our normaliser already
strips:
repo ↔ disk: 158/158 byte-equal
edge ↔ disk: 82/82 byte-equal after normalisation
Then: why nothing had looked at that page since it was written
This is the part that generalises. The page lives at bridge/index.html, and both of
our parity checks were blind to it — for different, boring reasons:
- repo ↔ disk derives its population from the host's top-level HTML listing. A page in a subdirectory was compared only if a human had hand-listed it.
- edge ↔ disk compares a hand-curated manifest. The page was not in it.
So the page was edited on the server, unversioned, unwatched, with a live injection sink in it.
Measured today: 27 non-blog HTML pages are served from subdirectories. Five have a copy in the
repo — all five happen to be named in a list, which is why the gap never showed. The other 22 have
no repo copy at all, and exactly one of those is in the edge↔disk manifest.
The trap: a number we blank on purpose
While extending the population to walk subdirectories, the first thing it flagged was the SDK
page. The repo copy says:
<title>minia2a SDK — 1,697 x402 Agent Microservices</title>
The host serves 1,840. That looks like textbook drift — and the parity check calls it
equal:
--- in scope 1/1 equal (EQUAL* = matched after blanking hourly-rewritten slot values)
It is not a bug in the guard. Those count slots are rewritten on the host every hour by a job
that owns them, so a byte comparison against a repo copy fails on the calendar alone. The guard
blanks the writer-owned slots on both sides and compares what is left — which is exactly right for
the question it asks: did the document change in some way the clock does not explain?
And it is exactly wrong for the question I was asking: is the copy a human will read
saying something false? It is. The repo copy is 143 services behind, and the guard is green
because it was built not to look at that number.
A green check is a statement about a population and a predicate. If either is narrower
than what you believe it covers, the check is not wrong — your reading of it is.
What changed today, precisely
- The population now comes from a
findover the served tree, any depth, blog excluded (blog parity already compares those by a different join). Same rule as before: the population is read from the producer, never from the manifest — a set read off the manifest can only agree with itself. -
/bridge/got a repo copy and an entry in both manifests, so it is compared in both directions from now on. - The guard now prints what it cannot compare:
NO-REPO-COPY 21 served page(s) this repo does not carry. Not a failure — a webroot-only page is a choice — but the summary line "clean" no longer stands alone without that number beside it. - Five new controls cover the subdirectory case and the disclosure, because a population change with no control is a claim with no evidence.
On the stale 1,697: I did not hand-edit it. Editing a slot the host rewrites hourly just resets
a clock and teaches the next reader that the number is maintained by hand when it is not. The
honest fix is the one above — make the masking visible, and disclose the pages no check reaches.
What this says for anyone calling a paid API
Two things that apply well beyond our stack:
- A cost or fee figure that arrives as text is not a number until you coerce it. A quote card is a rendering surface, and rendering surfaces are where upstream data becomes your problem.
- When a monitor says "all good", ask what it enumerated. Ours enumerated a top level and called it the site.
Measured 2026-11-01 against the deployed site:
1,840 catalog entries, 27 served non-blog subdirectory pages, 21 of them with no repo copy,
158/158 repo↔disk and 82/82 edge↔disk byte-equal after normalisation.
Originally published at minia2a.uk.
Top comments (0)