Skip to content

docs: eight inspect-tool claims the code refutes, and a guard derived from it - #617

Merged
Zyrtnin merged 2 commits into
mainfrom
docs/inspect-tool-concept-claims
Sep 4, 2026
Merged

docs: eight inspect-tool claims the code refutes, and a guard derived from it#617
Zyrtnin merged 2 commits into
mainfrom
docs/inspect-tool-concept-claims

Conversation

@Zyrtnin

@Zyrtnin Zyrtnin commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Stacked on #604. Eight claims in the inspect tool's own concept doc, all verified against the code on that branch — none had been fixed by parallel work.

# claim reality
1 The structural qualifier is "not optional and not suppressible" NOTES has 10 exact keys; p2pkh, unknown, error and — because the lookup is exact — op_return-msg and both hashmark types get nothing. _render_txid_human emits no qualifier on any row
2 "The OP_RETURN classifier does not interpret the payload" It decodes the msg tag to UTF-8, decodes HASHMARK, and calls verify_attestation — secp256k1 recovery
3 "SRI … catches a jsdelivr compromise before WASM ever runs" One integrity=, on the loader. Everything the loader then fetches from that origin carries none
4 The manifest check holds "even if the GitHub Pages deploy is compromised" Every digest comes from an unhashed same-origin manifest.json, written in the same job as the wheels. It catches a partial or corrupted deploy; it cannot defend that origin against itself
5 Three "source of truth" links Point at glyph_cmds.py; the code is in glyph_inspect.py
6 "it does not contact an indexer" The page opens a WebSocket and sends blockchain.transaction.get
7 Seven tx-shape banner triggers Four wrong, and six reveal-metadata banners were undocumented entirely
8 Two type lists One omitted nine values; the other omitted op_return-msg, both hashmark types and error

On 3 and 4 it was asked to be precise in both directions — overstating a weakness is its own inaccuracy on a page people use to decide whether to trust the tool. It states what each mechanism does catch as well as what it doesn't.

It found a real code defect

verify_attestation needs coincurve, which Pyodide doesn't install — so a HashMark record did not classify at all in the browser. Proved with a meta-path blocker. That is fixed separately in #615, which also caught a second path of the same shape that I had introduced myself.

Two smaller ones, reported not fixed: refresh-pyodide.sh writes to a path that doesn't exist, and a banner says "WAVE support in pyrxd is currently deferred" while pyrxd.glyph.wave ships and --verify-wave uses it.

The guard is derived, not hand-kept

test_inspect_concept_doc_matches_the_code.py (21 tests): repo-relative links and backticked symbols come from the document; the script type values are AST-extracted from _inspect_core.py, including the constant head of the two f-string assignments (p2pkh-, op_return-hashmark-v) that a literal-only scan would miss.

Proved load-bearing without editing the tree — fed the pre-fix doc, it fails on four separate counts. And tested against instances it was not built from: pointed at a scratch classifier carrying two invented types, it extracted and failed on both.

🤖 Generated with Claude Code

Mudwood Labs and others added 2 commits September 3, 2026 21:47
`docs/concepts/glyph-inspect-tool.md` listed this under the tool's threat model:

  Token names and tickers are run through the TR39 confusables skeleton check
  (`looks_confusable_with_latin`) — a Cyrillic-spoofed "USDC" is flagged with a
  warning banner before the user sees the rendered metadata.

`looks_confusable_with_latin` had NO PRODUCTION CALLER. Repo-wide it was its own
definition, a facade re-export, and its tests. The CLI ran no confusables check
at all; the browser page used a weaker script-mixing heuristic that fires on any
wholly non-Latin name.

Wired rather than downgraded, because the doc describes a real protection and the
function already exists and is tested. Computed once in `_inspect_core` so both
surfaces read one verdict, and the CLI prints it ABOVE the field it applies to —
a look-alike warning printed after the name is one the reader has already acted
on.

IT REPORTS MIMICRY, NOT FOREIGNNESS. "トークン" and "中文" are in the checker's
own not-flagged examples; "USDС" with a Cyrillic С, "раypal" and small-caps
"ᴜꜱᴅᴄ" are flagged. A warning firing on every legitimate Japanese token is the
false positive that trains a reader to ignore the real one, which this repo names
as a hazard in as many words elsewhere.

A COLLISION FOUND BY LOOKING. `glue.py` produces warnings under the same
`display_warnings` key from its heuristic and ASSIGNED it, so on the browser path
the stronger check's findings would have been silently replaced. Two producers,
one field name, last writer wins. It now seeds from the classifier and uses
setdefault, so the more specific TR39 reason survives where both fire.

MY OWN TEST SUITE FAILED THE RULE IT WAS WRITTEN FOR. The first version called
`_confusable_warnings` directly and nothing else: unwiring it from the classifier
— restoring the exact defect, a capability with no production caller — left all
16 tests green. A suite about a reachability bug that could not detect one.
Reachability tests now build a real reveal transaction and read the warning out
of `_classify_raw_tx`; that plant fails 1.

Three plants, each caught: unwiring the classifier, dropping the CLI rendering,
and restoring the bridge overwrite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`docs/concepts/glyph-inspect-tool.md` is the public explanation of what the
Glyph inspect tool proves, so its wrong sentences are the expensive kind — and
seven of them were wrong. Each claim below was checked against the code on this
branch before the sentence was touched.

The two security claims, stated precisely because overstating a weakness is its
own inaccuracy:

* "SRI on the loader catches a jsdelivr compromise before WASM ever runs."
  `index.html` carries exactly one `integrity=` attribute, on `pyodide.js`.
  `inspect.js` then calls `loadPyodide({indexURL: ".../pyodide/v0.26.4/full/"})`,
  and the loader fetches `pyodide.asm.wasm`, `pyodide.asm.js`, `python_stdlib.zip`
  and its packages from that path with no integrity attribute. So the check
  catches a tampered LOADER, not a jsdelivr compromise, and WASM does run from
  unverified bytes.

* "everything the page loads at runtime is integrity-checked", beside a wheel
  check said to hold "even if the GitHub Pages deploy is compromised".
  `loadManifest` fetches `manifest.json` with no hash, and every expected
  SHA-256 comes out of that same unauthenticated same-origin file — as does
  `inspect.js` itself. It is a deploy-integrity check (did these bytes come
  from the run that wrote this manifest?), which does catch a partial, stale
  or corrupted deploy and a manifest trying to redirect an install off-origin
  or supply a malformed digest. It cannot defend that origin against itself.
  The page now says which is which.

The rest:

* "The OP_RETURN classifier does not interpret the payload — it does not assume
  CBOR, ASCII, protocol-tag prefixes, or anything else." It decodes the Photonic
  `msg` tag to UTF-8, decodes the HASHMARK magic, and calls `verify_attestation`
  — a secp256k1 recovery checked against the committed signer. Documented,
  including what a `valid` attestation does and does not establish.

* "Every classified output carries a structural-match qualifier … not optional
  and not suppressible." `_structuralQualifierNote` is an exact-key lookup over
  ten keys, so `p2pkh`, `unknown` and `error` get nothing and the hyphenated
  `op_return-msg` / `op_return-hashmark-v*` lose even the generic OP_RETURN
  note; three more types carry the classifier's `note` instead. On the CLI,
  `_render_txid_human` emits no qualifier on any row, for any type — the
  surface where most people meet the tool. Replaced with the coverage table.

* "it does not contact an indexer." `--fetch` / `--resolve` issue
  `blockchain.transaction.get` to ElectrumX (the browser over a WebSocket), and
  `--verify-wave` does a name lookup. The intent — no classification comes from
  a server — is now stated as that, with the network paths named.

* Four of the tx-shape banner triggers were described wrongly, and half the
  banners were missing. FT deploy is `commit-ft && commit-nft`, not "paired with
  an FT or NFT singleton"; the dMint deploy reveal fires on any `dmint` output
  with `height === 0`, never comparing `token_ref`; the claim banner never
  checks `contract_ref` against the inputs. The six reveal-metadata marker
  banners and the commit-ft-without-commit-nft case were undocumented. Table
  rebuilt in `_detectTxShape`'s evaluation order, since it returns on first
  match.

* Three "source of truth" links sent readers to `src/pyrxd/cli/glyph_cmds.py`
  for `inspect_cmd` and `_render_script_human`; both live in
  `src/pyrxd/cli/glyph_inspect.py`. The path existed, so nothing caught it.

* Both type lists were stale, and the shorter one was missing nine values.

Also recorded, because it is a real defect the doc had been implying away:
`verify_attestation` imports `pyrxd.keys` (and so `coincurve`) inside the
function body, and nothing installs `coincurve` under Pyodide. Verified by
blocking the import and calling `_inspect_script` on the two mainnet HashMark
vectors — both raise `ModuleNotFoundError`. In the browser a HashMark OP_RETURN
therefore fails to classify; in a fetched tx that row degrades to `type=error`.
The Pyodide-clean test constrains the import graph only, so it passes. No code
is changed here; the page now says the gap exists rather than claiming
bug-for-bug parity.

The new test derives all three of its sets rather than hand-keeping them: links
and symbols from the document, and the script `type` values AST-extracted from
`_inspect_core` (including the constant head of the two f-string assignments).
Each check asserts its own set is non-empty first. Run against the pre-fix page
it fails on the symbol attribution, on `op_return-msg`, on `error` and on the
`op_return-hashmark-v` family; against a classifier copy carrying two invented
types it has never seen, it extracts both and fails on both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Zyrtnin
Zyrtnin merged commit 84bba2f into main Sep 4, 2026
19 checks passed
@Zyrtnin
Zyrtnin deleted the docs/inspect-tool-concept-claims branch September 4, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant