From 5d33ee748eeb980fb2144814242175ca81336567 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 1 Aug 2026 12:58:38 +0000 Subject: [PATCH] =?UTF-8?q?fix(moshpit):=20letters=20and=20digits=20only?= =?UTF-8?q?=20=E2=80=94=20no=20dashes,=20matching=20the=20registry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../ai-sidebar/moshpit-drift.test.js | 6 +++++ apps/desktop/extensions/ai-sidebar/moshpit.js | 9 ++++++- .../extensions/ai-sidebar/moshpit.test.js | 9 +++++++ apps/desktop/src/moshpit-resolve.test.ts | 25 +++++++++++++++++++ apps/desktop/src/moshpit-resolve.ts | 9 ++++++- 5 files changed, 56 insertions(+), 2 deletions(-) diff --git a/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js b/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js index fc2bc1a..bd46064 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit-drift.test.js @@ -23,6 +23,12 @@ import * as pkg from '@moshcoder/moshpit-resolve'; const HOSTNAMES = [ 'blue.eggs', 'a.b.c', '1.2.3.4', 'localhost', '', 'eggs', 'mosh.eggs', 'blue.420', '420.blue', '1.420', '192.168', 'x.y', 'A.EGGS.', + // Dashes. The list carried none, so a divergence on whether a dash may + // appear inside a label read as green across all three implementations. + 'lazy-loaded', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', ]; const LOOKUPS = [ diff --git a/apps/desktop/extensions/ai-sidebar/moshpit.js b/apps/desktop/extensions/ai-sidebar/moshpit.js index 4a2078a..eb4233d 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit.js @@ -128,7 +128,14 @@ export function parseRegistryName(hostname) { const parts = host.split('.'); if (parts.length !== 2) return null; const [label, tld] = parts; - const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; + // Letters and digits only, no dashes — the same rule the registry enforces. + // A dash is the cheapest way to mint a near-miss of an ending someone else + // holds (`.cryp-to` beside `.crypto`), and this namespace is one level deep + // and first come first served, so there is nothing to appeal into. Keeping + // this identical to the registry matters more than the rule itself: a name + // this accepts and the registry rejects forwards to a page saying it does + // not exist. + const LABEL = /^[a-z0-9]{1,63}$/; if (!LABEL.test(label) || !LABEL.test(tld)) return null; return { label, tld }; } diff --git a/apps/desktop/extensions/ai-sidebar/moshpit.test.js b/apps/desktop/extensions/ai-sidebar/moshpit.test.js index 8b74628..ec51a7f 100644 --- a/apps/desktop/extensions/ai-sidebar/moshpit.test.js +++ b/apps/desktop/extensions/ai-sidebar/moshpit.test.js @@ -23,6 +23,15 @@ const HOSTNAMES = [ 'mosh', '', 'x.y', + // Dashes. The list carried none, so a change to whether a dash may appear in + // a label could land in one implementation and not the other and every + // assertion below would still agree. + 'lazy-loaded', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', + '-bad.eggs', + 'bad-.eggs', ]; describe('moshpit.js is faithful to moshpit-resolve.ts', () => { diff --git a/apps/desktop/src/moshpit-resolve.test.ts b/apps/desktop/src/moshpit-resolve.test.ts index 8177e96..c0a9e46 100644 --- a/apps/desktop/src/moshpit-resolve.test.ts +++ b/apps/desktop/src/moshpit-resolve.test.ts @@ -307,3 +307,28 @@ describe('decideResolution — parking unpointed names', () => { expect(d.url).toBe('https://my.park/n/california.oranges'); }); }); + +describe('dashes are not part of a Moshpit name', () => { + // Kept identical to the registry on purpose. A name this accepts and the + // registry rejects forwards the tab to a page saying it does not exist, + // which is a worse failure than refusing it here. + it('refuses a dash anywhere in either half', () => { + for (const host of [ + 'lazy-loaded.eggs', + 'blue.lazy-loaded', + 'register-me.eggs', + 'a-b.c-d', + '-bad.eggs', + 'bad-.eggs', + ]) { + expect(parseRegistryName(host), host).toBeNull(); + } + }); + + it('still accepts what the registry accepts', () => { + expect(parseRegistryName('california.oranges')).toEqual({ label: 'california', tld: 'oranges' }); + expect(parseRegistryName('blue.420')).toEqual({ label: 'blue', tld: '420' }); + expect(parseRegistryName(`${'a'.repeat(63)}.eggs`)).toEqual({ label: 'a'.repeat(63), tld: 'eggs' }); + expect(parseRegistryName(`${'a'.repeat(64)}.eggs`)).toBeNull(); + }); +}); diff --git a/apps/desktop/src/moshpit-resolve.ts b/apps/desktop/src/moshpit-resolve.ts index 9041097..8c27e83 100644 --- a/apps/desktop/src/moshpit-resolve.ts +++ b/apps/desktop/src/moshpit-resolve.ts @@ -237,7 +237,14 @@ export function parseRegistryName(hostname: string): { label: string; tld: strin // narrow an array to a 2-tuple from a length check. An empty label would // fail LABEL.test() anyway, so this guard changes no behavior. if (!label || !tld) return null; - const LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; + // Letters and digits only, no dashes — the same rule the registry enforces. + // A dash is the cheapest way to mint a near-miss of an ending someone else + // holds (`.cryp-to` beside `.crypto`), and this namespace is one level deep + // and first come first served, so there is nothing to appeal into. Keeping + // this identical to the registry matters more than the rule itself: a name + // this accepts and the registry rejects forwards to a page saying it does + // not exist. + const LABEL = /^[a-z0-9]{1,63}$/; if (!LABEL.test(label) || !LABEL.test(tld)) return null; return { label, tld }; }