fix: enforce subpath match for staging domain validation#2590
Conversation
…h prod sites `areDomainsSameAsBase` previously only checked the registrable domain (e.g. nba.com), so a staging domain with the wrong subpath—or no subpath at all—was accepted for a subpath prod site. Now `getPathname` extracts the pathname from both the prod URL and each staging URL (normalising trailing slashes) and requires them to match. Root-domain prod sites are unaffected: both sides resolve to `/`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
MysticatBot
left a comment
There was a problem hiding this comment.
Hey @dhavkuma,
Verdict: Request changes - one robustness gap in the new getPathname helper needs addressing.
Changes: Adds subpath matching to staging domain validation and a CDN routing guard that rejects subpath sites from host-level auto-routing (2 files).
Must fix before merge
- [Important]
getPathnamereturns'/'on unparseable input, making parse failure indistinguishable from a legitimate root path -src/controllers/llmo/llmo.js:1865(details inline)
Non-blocking (3): minor issues and suggestions
- nit: Add a test exercising a malformed
baseURLreaching the CDN guard catch block - fixes the codecov/patch failure and documents the intended fall-through behavior -src/controllers/llmo/llmo.js:1495 - nit: The trailing-slash test asserts
status !== 400but the guard returns 200 when it rejects - assert the response body does NOT include "subpath sites" instead, which directly proves the guard did not fire -test/controllers/llmo/llmo.test.js:6281 - suggestion: The CDN guard duplicates the "prepend https:// if missing then new URL()" pattern that
getPathnamealready encapsulates - consider callinggetPathname(baseURL) !== '/'instead of re-implementing inline -src/controllers/llmo/llmo.js:1486
| try { | ||
| return new URL(`https://${urlString}`).pathname.replace(/\/$/, '') || '/'; | ||
| } catch { | ||
| return '/'; |
There was a problem hiding this comment.
issue (blocking): getPathname returns "/" on unparseable input, making the return value ambiguous.
What: The inner catch { return '/'; } means any garbage string (empty, whitespace, protocol-only like "https://") normalizes to "/". When both prod and stage URLs are malformed, getPathname returns "/" for both and the pathname comparison passes vacuously. The only remaining guard is getDomain, which may or may not reject the same garbage.
Why it matters: The function has an unclear contract - callers cannot distinguish "this is a root path" from "I could not parse this at all." If getDomain ever gains a similarly lenient fallback, validation would pass for two broken URLs.
Fix: Return a sentinel (e.g. null) from the final catch and treat it as a non-match in areDomainsSameAsBase:
function getPathname(urlString) {
try {
return new URL(urlString).pathname.replace(/\/$/, '') || '/';
} catch {
try {
return new URL(`https://${urlString}`).pathname.replace(/\/$/, '') || '/';
} catch {
return null;
}
}
}
// In areDomainsSameAsBase:
return urlList.every(
(stageURL) => {
const stagePath = getPathname(stageURL);
return stagePath !== null && getDomain(stageURL) === prodDomain && stagePath === prodPath;
},
);This also makes the CDN guard clearer if you reuse it there.
There was a problem hiding this comment.
Fixed in b145ff1 — getPathname now returns null in the final catch. areDomainsSameAsBase guards against null before comparing paths, so two malformed URLs can no longer match vacuously. The new malformed-URL test covers the CDN guard catch block (fall-through behavior) and the trailing-slash test now asserts the response body excludes "subpath sites" rather than checking the status code.
…ard tests - getPathname now returns null instead of '/' for malformed URLs, making the parse-failure case distinguishable from a genuine root path - areDomainsSameAsBase treats a null pathname as a non-match - Trailing-slash test now asserts response body excludes 'subpath sites' instead of checking status (which the guard also returns as 200) - New test covers the CDN guard catch block with a malformed baseURL, exercising the fall-through behaviour to the probe step Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
This PR will trigger a patch release when merged. |
| * @param {string} prodBaseURL the production base URL to match against. | ||
| * @returns {boolean} true if all URLs share the same domain as prodBaseURL | ||
| */ | ||
| function getPathname(urlString) { |
There was a problem hiding this comment.
try to look for reusable function
| return urlList.every( | ||
| (stageURL) => { | ||
| const stagePath = getPathname(stageURL); | ||
| return stagePath !== null && getDomain(stageURL) === prodDomain && stagePath === prodPath; |
There was a problem hiding this comment.
consider the cases
- override urls
There was a problem hiding this comment.
Also onboard stagin domain command should not enforce this check yet
Summary
areDomainsSameAsBasepreviously only checked the registrable domain (e.g.nba.com), so a staging URL likestaging.nba.comwas accepted for a prod site atnba.com/kingsgetPathnamehelper that extracts and normalises the pathname from both URLs (handles bare hostnames, trailing slashes)areDomainsSameAsBasenow also requirespathname(stage) === pathname(prod)— root-domain prod sites are unaffected (both sides normalise to/)Before:
staging.nba.comaccepted for prodnba.com/kings✗After: only
staging.nba.com/kingsaccepted for prodnba.com/kings✓Test plan
createOrUpdateStageEdgeConfigtests passnpm testpasses (exit 0)Related
Companion UI PR: adobe/project-elmo-ui (same branch name)
🤖 Generated with Claude Code