fix: desentinelize the render context before postRender, credentialless cross-origin prefetch

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 <noreply@anthropic.com>
pull/5406/head
Divyansh Singh 2 weeks ago
parent f8a7062dd1
commit 6915961ae6

@ -18,6 +18,14 @@ export default defineConfig({
build: { assetsInlineLimit: 0 } build: { assetsInlineLimit: 0 }
}, },
// user hooks must only ever see final urls, never the build sentinel // 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 }) { transformHead({ assets, head, content }) {
if ((JSON.stringify([assets, head]) + content).includes('__VP_BASE__')) { if ((JSON.stringify([assets, head]) + content).includes('__VP_BASE__')) {
throw new Error('sentinel leaked to transformHead') throw new Error('sentinel leaked to transformHead')

@ -20,7 +20,8 @@ const viaDOM = (url: string) => {
const viaXHR = (url: string) => { const viaXHR = (url: string) => {
const req = new XMLHttpRequest() 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() req.send()
} }

@ -39,9 +39,23 @@ export async function renderPage(
) { ) {
const routePath = `/${page.replace(/\.md$/, '')}` 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 // render page
const context = await render(routePath) 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 (await config.postRender?.(context)) ?? context
// add used social icons to the set // add used social icons to the set
@ -75,23 +89,12 @@ export async function renderPage(
const siteData = resolveSiteDataByRoute(config.site, page, pageData.filePath) 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 assetUrl = (file: string) => (config.assetsBase ?? pageBase) + file
const assetsCrossOrigin = const assetsCrossOrigin =
config.assetsBase && EXTERNAL_URL_RE.test(config.assetsBase) config.assetsBase && EXTERNAL_URL_RE.test(config.assetsBase)
? ' crossorigin' ? ' 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 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 title: string = createTitle(siteData, pageData)
const description: string = pageData.description || siteData.description const description: string = pageData.description || siteData.description

Loading…
Cancel
Save