Read a realm identifier without assuming it is a URL - #5931
Conversation
Four sites take a realm identifier and immediately ask a URL question of it. Each is safe today only because the realm list happens to hold URL forms, and each fails differently the moment an entry is a registered prefix. `routes/index.gts` matched a card path against a realm path through `new URL(realm).pathname`, which throws for a prefix. The realm picker derived its label from the last path segment inside a `try`, so a prefix answered "Unknown Workspace" rather than the realm's name — a wrong label, no error. Both now take the segments of whichever form they are handed, through one helper that says why parsing loses the prefix case. `getRealmServersForRealms` skipped realms served by the test realm's origin. A prefix has no origin, so the identifier resolves through the VirtualNetwork before the comparison instead of being parsed as though it were already a URL. No behaviour changes: every identifier these see is URL-form today, and the `Integration | Store` module reads 81 passed against main's 81. This is the form-agnostic groundwork on its own, separated from the canonical-seed change that would depend on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 2h 4m 5s ⏱️ -36s Results for commit bd52016. ± Comparison against earlier commit 3402568. Realm Server Test Results 1 files ±0 1 suites ±0 18m 8s ⏱️ +8s Results for commit bd52016. ± Comparison against earlier commit 3402568. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 340256853a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .filter((part) => part !== ''); | ||
| // A realm identifier may be a URL or a registered prefix, and only | ||
| // the first has a pathname; take the segments of whichever it is. | ||
| let realmPathParts = realmIdentifierSegments(realmUrl); |
There was a problem hiding this comment.
Resolve prefixes before matching hosted routes
When hostsOwnAssets is enabled and this list starts containing a registered prefix, these segments do not represent the realm's mounted URL path. For example, @cardstack/base/ produces ['@cardstack', 'base'], while its mapped URL is mounted at /base/, so a normal base/foo route does not match and is resolved against the default realm instead. If the card path does contain the prefix segments, the later new URL(..., realmUrl) receives the non-URL prefix and throws. Resolve the identifier through virtualNetwork.toURL() before extracting the pathname and reuse that resolved URL as the base.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[Claude Code 🤖] Correct on both counts, and the first one is the more interesting: my helper was answering the wrong question here. Fixed in bd52016.
Verified rather than assumed:
@cardstack/base/ segments -> ["@cardstack", "base"]
https://x/base/ pathname -> ["base"]
new URL('/base/foo', '@cardstack/base/') -> TypeError: Invalid URL
So a base/foo route would have matched nothing and fallen through to the default realm, and the base argument throws — silent wrong-realm resolution in one direction, a crash in the other.
The underlying mistake was conflating two questions behind one helper. What is this realm called and where is this realm mounted have the same answer for a URL identifier and different answers for a prefix, so a single "segments" function reads as correct right up until the forms diverge. This route asks the second question, so it now resolves each identifier through virtualNetwork.toURL() once and both matches on the resulting pathname and reuses it as the base — which is your suggestion exactly.
The helper stays for the realm picker, which genuinely is naming rather than locating: both forms end in the segment that names the realm, so a label built from it is right either way. Its doc now scopes it to that and points anything matching a request path at the VirtualNetwork, so the next caller does not repeat the conflation.
One consequence worth noting: with the resolution in place the site no longer needs its no-url-from-realm-identifier suppression, so that came out too — --report-unused-disable-directives would have caught it regardless, but it is a small sign the change is the right shape.
Locally against an environment-mode stack: Integration | Store 81/0 and Acceptance | basic tests 4/0, matching main.
Deriving segments from the identifier answers the wrong question here. The route asks which realm *serves* a card path, and a registered prefix does not carry that: `@cardstack/base/` names two namespace segments while the realm it maps to is mounted at `/base/`, so a `base/foo` route would match nothing and fall through to the default realm. The base argument below has the same requirement — a prefix is not a valid base URL. Resolving each identifier once fixes both: the match compares mounted paths, and the resolved URL is what `new URL` is handed. The segment helper stays for the realm picker, which is naming a realm rather than locating one — both forms end in the segment that names it. Its doc now says that is the whole of its remit, and points anything matching a request path at the VirtualNetwork instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude: Four sites take a realm identifier and immediately ask a URL question of it. Each is safe today only because the realm list happens to hold URL forms, and each fails differently the moment an entry is a registered prefix.
routes/index.gtsmatched a card path against a realm path throughnew URL(realm).pathname, which throws for a prefix. The realm picker derived its label from the last path segment inside atry, so a prefix answered "Unknown Workspace" rather than the realm's name — a wrong label, no error. Both now take the segments of whichever form they are handed, through one helper that says why parsing loses the prefix case.getRealmServersForRealmsskipped realms served by the test realm's origin. A prefix has no origin, so the identifier resolves through the VirtualNetwork before the comparison instead of being parsed as though it were already a URL.No behaviour changes: every identifier these see is URL-form today, and the
Integration | Storemodule reads 81 passed against main's 81. This is the form-agnostic groundwork on its own, separated from the canonical-seed change that would depend on it.