Skip to content

feat(moshpit): letters and digits only — no dashes in a name or an ending - #193

Merged
ralyodio merged 1 commit into
mainfrom
no-dashes
Aug 1, 2026
Merged

feat(moshpit): letters and digits only — no dashes in a name or an ending#193
ralyodio merged 1 commit into
mainfrom
no-dashes

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Why

A dash is the cheapest way to mint a near-miss of an ending someone else holds. .crypto and .cryp-to read as the same thing at a glance and sort next to each other.

That matters more here than it would elsewhere: the namespace is one level deep and first come first served. There's no second level to retreat to and no dispute process to appeal into, so a look-alike isn't a nuisance — it's the whole attack.

Blast radius, measured before changing anything

Of 4,593 endings currently claimed, exactly one contains a dash:

.lazy-loaded

— minted today while testing whether dashes were allowed. Nothing else in the namespace moves.

The rule

- const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
+ const LABEL = /^[a-z0-9]{1,63}$/;

Changed in both places at once, deliberately: apps/pwa/src/lib/moshpit-name.mjs (the registry, authoritative) and src/dns.mjs (parseRegistryName, the bridge). A name the bridge accepts and the registry rejects resolves to a page saying it does not exist — worse than either rule alone.

Unchanged: all-numeric endings (.420), all-numeric labels (123.420), the 63-character ceiling, and leading/trailing-dash rejection (now by construction).

Tests

  • CLI: 517 tests, 515 pass, 0 fail, 2 skipped
  • apps/pwa: 261 tests, 261 pass, 0 fail

One existing assertion encoded the old rule (normalizeTld("web3-agents")) and moved to the rejection case, which is the behaviour change showing up where it should.

Before merge

Two follow-ups this doesn't do:

  1. .lazy-loaded becomes unparseable once this lands — its page and any management of it break. It should be released first, or grandfathered. It's the only one.
  2. @moshcoder/moshpit-dns and moshcoder/moshpit-name carry their own copies of this regex and will drift until they get the same change.

🤖 Generated with Claude Code

…ding

A dash is the cheapest way to mint a near-miss of an ending someone else
holds. `.crypto` and `.cryp-to` read as the same thing at a glance and
sort next to each other, so allowing dashes turns every claimed ending
into a family of look-alikes worth squatting. The namespace is one level
deep and first come first served: there is no second level to retreat to
and no dispute process to appeal into, which makes the near-miss the
whole attack rather than a nuisance.

Blast radius measured before changing anything: of 4593 endings claimed,
exactly one contains a dash — `.lazy-loaded`, minted today while testing
whether dashes were allowed. Nothing else in the namespace moves.

The rule is changed in both places at once on purpose. A name the bridge
accepts and the registry rejects resolves to a page saying it does not
exist, which is a worse failure than either rule alone.

The cost is real names nobody can have. That is the trade, and it is
cheaper than policing look-alikes forever.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

ralyodio added a commit to profullstack/tronbrowser.dev that referenced this pull request Aug 1, 2026
…try (#66)

Both local copies of the name grammar accepted a dash inside a label,
which the registry is dropping (moshcoder/moshpit-name#6,
moshcoder/moshcode#193). A name accepted here and rejected there forwards
the tab to a page saying it does not exist, which is a worse failure than
refusing it up front.

Neither drift test would have caught this. moshpit.test.js compares the
extension port against the TypeScript reference over a shared hostname
list, and moshpit-drift.test.js compares both against the published
package — and neither list contained a dash inside a label. Leading and
trailing dashes were covered from the start; the case the rule actually
turns on was covered nowhere, so all three implementations could disagree
and every assertion still passed.

Both lists now carry it, and the anchor consequently fails against
@moshcoder/moshpit-resolve 0.1.1 with a real behavioural difference:
blue.lazy-loaded decides 'park' against the package and 'clearnet' here.
That is the guard working. It goes green once moshcoder/moshpit-resolve#3
publishes and the devDependency is bumped.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit ce2834d into main Aug 1, 2026
3 checks passed
@ralyodio
ralyodio deleted the no-dashes branch August 1, 2026 13:09
ralyodio added a commit to moshcoder/moshpit-dns that referenced this pull request Aug 1, 2026
The last copy of this rule, and right now the one breaking main in
moshcode: with moshcoder/moshcode#193 and #194 merged, its drift test
compares the vendored bridge against this package and fails on
blue.lazy-loaded — vendored says null, this says { blue, lazy-loaded }.

That is the guard working. It was predicted in #194 and is why the
publish has to lead the consumer, not follow it.

Matches moshcoder/moshpit-name#6 and moshcoder/moshpit-resolve#3. Of 4593
endings claimed, exactly one contains a dash.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ralyodio added a commit to moshcoder/moshpit-resolve that referenced this pull request Aug 1, 2026
A dash is the cheapest way to mint a near-miss of an ending someone else
holds. `.crypto` and `.cryp-to` read as the same thing at a glance, and
this namespace is one level deep and first come first served, so there is
no second level to retreat to and nothing to appeal into.

Matches moshcoder/moshpit-name#6 and moshcoder/moshcode#193. This copy
came to light because tronbrowser.dev anchors both of its local copies to
this package: with the rule changed there and not here, the anchor test
fails with a real behavioural difference — `blue.lazy-loaded` decides
'park' against this package and 'clearnet' against the new rule, which is
a tab sent somewhere else.

Blast radius, measured against the live registry: of 4593 endings claimed,
exactly one contains a dash.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ralyodio added a commit that referenced this pull request Aug 1, 2026
Unblocks main, which has been red since #193 merged.

The dash rule landed here (#193) and in the drift guard covering it
(#194) before the package carried it, so dns-drift correctly failed on
blue.lazy-loaded: vendored null, published { blue, lazy-loaded }. That
is the guard working.

0.2.2 carries the rule. The lockfile is the part that matters — CI runs
`pnpm install --frozen-lockfile`, so the ^0.2.1 range in package.json
never picked the new version up on its own; it installed 0.2.1 and kept
failing after the publish.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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