Skip to content

fix(approval): mark registry-sourced asset labels as unverified - #126

Open
dmnyc wants to merge 2 commits into
mainfrom
fix/113-registry-label-badge
Open

fix(approval): mark registry-sourced asset labels as unverified#126
dmnyc wants to merge 2 commits into
mainfrom
fix/113-registry-label-badge

Conversation

@dmnyc

@dmnyc dmnyc commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #113 (L-13).

Token-send approvals showed the registry ticker (and amount scaled by registry precision) with no signal that both come from the public asset registry — data an issued asset can steer. A Label: <ticker> · registry row now sits under the asset ID row, with the hover title spelling out that the name is unverified and the ID is the identifier. Exact base units were already shown in their own row, so a wrong precision can't hide the true amount.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@dmnyc

dmnyc commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented Aug 15, 2026

Copy link
Copy Markdown

Claude finished @dmnyc's task in 3m 7s —— View job


Review: registry label badge on token-send approvals

Good, minimal change that lands the L-13 recommendation, and the inline comment explains why the row exists rather than what it does — consistent with the rest of this file. No XSS surface (React escapes; the Jade path already esc()s at src/jade/jade.ts:97). A few things I'd tighten before merge.


1. The provenance marker is the part that gets truncated 🔴

Row renders its value with truncate unless wrap is set (src/sidepanel/screens/Approval.tsx:656), and the new value puts the attacker-controlled string first and the security marker last:

value={`${sendReview.assetTicker} · registry`}

Inside a max-w-sm card with the Label dt taking the left, a long ticker overflows and the ellipsis eats · registry — so the row degrades to Label SOMELONGTICKER… precisely in the case where the string is hostile. The badge disappears exactly when it matters most.

getAsset reads the ticker straight out of the parsed contract JSON with no length bound (src/engine/engine-core.ts:1198-1207). The public registry does enforce short tickers in practice, so this isn't a clean exploit — but the whole premise of this row is that the registry isn't trusted, so it shouldn't be trusted for length either.

Suggested shape:

value={`registry · ${sendReview.assetTicker.slice(0, 24)}`} "${sendReview.assetTicker}" comes from the public asset registry and is not verified  identify the asset by its ID above.`}

Leading with registry · makes the marker unclippable, and folding the raw ticker into the title keeps a truncated value recoverable on hover (right now the title is fixed text, so a clipped ticker can't be read at all).

Fix this →

2. On the in-wallet Send path, assetTicker often isn't from the registry 🟠

Send.tsx:108-113 resolves the label as:

KNOWN_ASSETS[assetId]?.label ?? assets[assetId]?.ticker ?? assets[assetId]?.name ?? shortenHex(assetId, 6, 6)

and passes it as assetTicker (src/sidepanel/screens/Send.tsx:287). So the new row can claim registry provenance for two things that don't have it:

  • Known assets — a USDt send renders USDt · registry with a "not verified" tooltip, but that label is hardcoded in KNOWN_ASSETS and ships with the wallet. Over-warning; wrong, but in the safe direction.
  • Unregistered assets — the fallback is shortenHex(assetId, 6, 6), so the row renders ce091c…a37bd2 · registry: a registry claim about a truncated copy of the asset ID that's already displayed in the Asset row directly above. Wrong and noise.

The dapp path is clean here — background/index.ts:1701 sets assetTicker: metadata?.ticker ?? null from getAsset only, and only for non-policy assets.

Cheapest correct fix is at the producer: have Send.tsx pass assetTicker only when the label actually came from assets[assetId] (registry), leaving it null for known assets and the hex fallback. Then this row means what it says on both paths without further conditions.

Fix this →

3. The unmarked ticker still leads the Amount row 🟡

Approval.tsx:100-101 still renders 1.00 USDt in the emphasized telemetry face, with the caveat living in a separate row below it. The spoof L-13 describes — a hostile issuance whose ticker mimics a real one — lands in the Amount row, and a user scanning only that row gets no signal. The Base units row does keep the true magnitude visible, so this isn't a funds-loss gap, but it's worth deciding deliberately whether the marker should attach to the amount rather than sit under the asset ID. Design call; not blocking.

4. Row isn't gated on assetId, but its tooltip promises one 🟡

The tooltip says "identify the asset by its ID above", while the condition is only sendReview?.assetTicker. Both producers currently spread assetId/assetTicker together so there's no live bug — but an explicit gate makes the tooltip structurally true rather than true by convention:

{sendReview?.assetId && sendReview.assetTicker && (

5. No test 🟡

src/sidepanel/screens/Approval.test.ts already does renderToStaticMarkup assertions on this component. Two cheap cases would pin the behavior: the row renders for a token send with a ticker, and it does not render for an LBTC send. If you take fix #1, add a long-ticker case asserting registry survives in the rendered value.

6. Nit — unvalidated ticker type upstream

src/engine/engine-core.ts:1205 does ticker: c.ticker ?? null with no typeof c.ticker === "string" guard, while precision right below it does get one. A contract with "ticker": 123 yields an AssetInfo.ticker that violates its declared type. Both render sites use template literals so nothing crashes, but this PR adds the second such site — worth a guard while you're in the area.


Verification

I could not run pnpm typecheck or pnpm test — dependencies aren't installed in this checkout and the install command wasn't permitted, so nothing here is machine-verified. The change adds no imports and Row already accepts title, so type risk is low; CI should cover it.

Verdict: the direction is right and #3/#4/#5/#6 are all optional. I'd treat #1 (marker clipped by truncation) and #2 (false provenance claim for known and unregistered assets on the Send path) as worth fixing before merge — #2 in particular means the row currently asserts something untrue for the most common in-wallet token send.
· branch fix/113-registry-label-badge

@dmnyc

dmnyc commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Review response — fd7c6a1:

Typecheck + 327 tests pass locally.

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.

[L-13] Badge registry-sourced asset labels as unverified in approvals

1 participant