From 6915961ae600a9f8bbfcac43765d3c1dbf684629 Mon Sep 17 00:00:00 2001 From: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> Date: Fri, 28 Aug 2026 04:36:28 +0530 Subject: [PATCH] fix: desentinelize the render context before postRender, credentialless cross-origin prefetch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit postRender received content and teleports still carrying the relative-base sentinel that transformHead/transformHtml already had resolved; the swap now happens right after the ssr render, covering every teleport entry. The XHR prefetch fallback sent credentialed requests, which a CDN answering with a wildcard allow-origin rejects — cross-origin urls now go credentialless, matching the module fetch and the link-prefetch path. Co-Authored-By: Claude Fable 5 --- __tests__/base/fixture/.vitepress/config.ts | 8 ++++++ src/client/app/composables/preFetch.ts | 3 ++- src/node/build/render.ts | 27 ++++++++++++--------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/__tests__/base/fixture/.vitepress/config.ts b/__tests__/base/fixture/.vitepress/config.ts index e274c403..74557091 100644 --- a/__tests__/base/fixture/.vitepress/config.ts +++ b/__tests__/base/fixture/.vitepress/config.ts @@ -18,6 +18,14 @@ export default defineConfig({ build: { assetsInlineLimit: 0 } }, // user hooks must only ever see final urls, never the build sentinel + postRender(context) { + if (JSON.stringify(context.teleports ?? {}).includes('__VP_BASE__')) { + throw new Error('sentinel leaked to postRender teleports') + } + if (context.content.includes('__VP_BASE__')) { + throw new Error('sentinel leaked to postRender') + } + }, transformHead({ assets, head, content }) { if ((JSON.stringify([assets, head]) + content).includes('__VP_BASE__')) { throw new Error('sentinel leaked to transformHead') diff --git a/src/client/app/composables/preFetch.ts b/src/client/app/composables/preFetch.ts index 517dfaf0..35c23a81 100644 --- a/src/client/app/composables/preFetch.ts +++ b/src/client/app/composables/preFetch.ts @@ -20,7 +20,8 @@ const viaDOM = (url: string) => { const viaXHR = (url: string) => { const req = new XMLHttpRequest() - req.open('GET', url, (req.withCredentials = true)) + req.open('GET', url, true) + req.withCredentials = !EXTERNAL_URL_RE.test(url) req.send() } diff --git a/src/node/build/render.ts b/src/node/build/render.ts index a7ae6946..bab5268b 100644 --- a/src/node/build/render.ts +++ b/src/node/build/render.ts @@ -39,9 +39,23 @@ export async function renderPage( ) { const routePath = `/${page.replace(/\.md$/, '')}` + const relativeBase = isRelativeBase(config.site.base) + const pageBase = relativeBase ? relativePathToRoot(page) : config.site.base + // user hooks must never see the build sentinel + const desentinel = (value: string) => + relativeBase ? value.replaceAll(RELATIVE_BASE_SENTINEL, pageBase) : value + // render page const context = await render(routePath) - let { content, teleports, vpSocialIcons } = + if (relativeBase) { + context.content = desentinel(context.content) + if (context.teleports) { + for (const key in context.teleports) { + context.teleports[key] = desentinel(context.teleports[key]) + } + } + } + const { content, teleports, vpSocialIcons } = (await config.postRender?.(context)) ?? context // add used social icons to the set @@ -75,23 +89,12 @@ export async function renderPage( const siteData = resolveSiteDataByRoute(config.site, page, pageData.filePath) - const relativeBase = isRelativeBase(siteData.base) - const pageBase = relativeBase ? relativePathToRoot(page) : siteData.base - const assetUrl = (file: string) => (config.assetsBase ?? pageBase) + file const assetsCrossOrigin = config.assetsBase && EXTERNAL_URL_RE.test(config.assetsBase) ? ' crossorigin' : '' - - // user hooks must never see the build sentinel - const desentinel = (value: string) => - relativeBase ? value.replaceAll(RELATIVE_BASE_SENTINEL, pageBase) : value const pageAssets = relativeBase ? assets.map(desentinel) : assets - if (relativeBase) { - content = desentinel(content) - if (teleports?.body) teleports.body = desentinel(teleports.body) - } const title: string = createTitle(siteData, pageData) const description: string = pageData.description || siteData.description