From 245e637d3aa68df1f56733ec90a43b195dd96221 Mon Sep 17 00:00:00 2001 From: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com> Date: Thu, 18 Jun 2026 20:08:52 -0300 Subject: [PATCH] fix(core): escape author-controlled values in bundler attribute selectors bundleToSingleHtml interpolated raw attribute values into querySelector attribute selectors at three sites: the external-script dedup (htmlBundler.ts:627, :828) and the sub-composition root lookup (:860). A value with a double quote (a quoted query param in a +`, + "compositions/scene.html": ``, + }); + + const bundled = await bundleToSingleHtml(dir); + // Does not throw, and the external script survives into the bundle. + expect(bundled).toContain(`cb=`); + }); + it("produces a self-contained runtime script when no HYPERFRAME_RUNTIME_URL is set", async () => { // Regression guard: hf#XXX. The bundler used to emit // when no runtime URL was configured. An diff --git a/packages/core/src/compiler/htmlBundler.ts b/packages/core/src/compiler/htmlBundler.ts index 538f0632ff..62ab48f9c0 100644 --- a/packages/core/src/compiler/htmlBundler.ts +++ b/packages/core/src/compiler/htmlBundler.ts @@ -624,7 +624,7 @@ export interface BundleOptions { */ function ensureExternalScriptTag(doc: Document, src: string): void { - if (doc.querySelector(`script[src="${src}"]`)) return; + if (doc.querySelector(`script${cssAttributeSelector("src", src)}`)) return; const el = doc.createElement("script"); el.setAttribute("src", src); doc.body.appendChild(el); @@ -825,7 +825,7 @@ export async function bundleToSingleHtml( continue; } } - if (!document.querySelector(`script[src="${extSrc}"]`)) { + if (!document.querySelector(`script${cssAttributeSelector("src", extSrc)}`)) { const extScript = document.createElement("script"); extScript.setAttribute("src", extSrc); document.body.appendChild(extScript); @@ -857,7 +857,7 @@ export async function bundleToSingleHtml( const hostIdentity = hostIdentityByElement.get(host); const runtimeCompId = hostIdentity?.runtimeCompositionId || compId; const innerDoc = parseHTMLContent(templateHtml); - const innerRoot = innerDoc.querySelector(`[data-composition-id="${compId}"]`); + const innerRoot = innerDoc.querySelector(cssAttributeSelector("data-composition-id", compId)); const authoredRootId = innerRoot?.getAttribute("id")?.trim() || null; const runtimeScope = runtimeCompId ? cssAttributeSelector("data-composition-id", runtimeCompId)