diff --git a/packages/xarc-subapp/src/browser/xarc-subapp-v2.ts b/packages/xarc-subapp/src/browser/xarc-subapp-v2.ts
index b0f0553a9..ffe90bc3f 100644
--- a/packages/xarc-subapp/src/browser/xarc-subapp-v2.ts
+++ b/packages/xarc-subapp/src/browser/xarc-subapp-v2.ts
@@ -182,7 +182,7 @@ export function xarcV2Client(
return {};
} else {
try {
- return JSON.parse(element.innerHTML);
+ return JSON.parse(element.innerHTML.replace(/<(\/?)script>/g, "<$1script>"));
} catch (err) {
console.error(msg, id, "- parse");
return {};
diff --git a/packages/xarc-subapp/src/node/init-v2.ts b/packages/xarc-subapp/src/node/init-v2.ts
index 9b0e1ddc3..caf66d7cb 100644
--- a/packages/xarc-subapp/src/node/init-v2.ts
+++ b/packages/xarc-subapp/src/node/init-v2.ts
@@ -1,11 +1,11 @@
/* eslint-disable no-console, max-statements, global-require, @typescript-eslint/no-var-requires */
import Path from "path";
-import { generateNonce, loadCdnMap, mapCdn, wrapStringFragment, urlJoin } from "./utils";
+import { loadCdnMap, mapCdn, wrapStringFragment, urlJoin } from "./utils";
import { WebpackStats } from "./webpack-stats";
import Crypto from "crypto";
import { AssetPathMap, InitProps } from "./types";
-import { SSR_PIPELINES } from "./utils";
+import { SSR_PIPELINES, safeStringifyJson } from "./utils";
/**
* Initialize all the up front code required for running subapps in the browser.
@@ -56,7 +56,7 @@ export function initSubApp(setupContext: any, setupToken: Partial<{ props: InitP
if (cdnMapData) {
const cdnMapJsonId = `cdn-map-${Crypto.randomBytes(8).toString("base64")}`;
cdnAsJsonScript = `
`;
cdnUpdateScript = `window.xarcV2.cdnUpdate({md:window.xarcV2.dyn("${cdnMapJsonId}")})
diff --git a/packages/xarc-subapp/src/node/server-render-pipeline.ts b/packages/xarc-subapp/src/node/server-render-pipeline.ts
index e6daef52c..0b9bbe0e2 100644
--- a/packages/xarc-subapp/src/node/server-render-pipeline.ts
+++ b/packages/xarc-subapp/src/node/server-render-pipeline.ts
@@ -3,6 +3,7 @@ import _ from "lodash";
import { SubAppRenderPipeline } from "../subapp/subapp-render-pipeline";
import { SubAppSSRData, SubAppFeatureResult, LoadSubAppOptions } from "../subapp/types";
import { ServerFrameworkLib } from "./types";
+import { safeStringifyJson } from "./utils";
// global name to store client subapp runtime, ie: window.xarcV1
// V1: version 1.
const xarc = "window.xarcV2";
@@ -94,16 +95,19 @@ export class SubAppServerRenderPipeline implements SubAppRenderPipeline {
const dataId = `${name}-initial-state-${Date.now()}-${++SubAppServerRenderPipeline.INITIAL_STATE_TAG_ID}`;
initialStateData = `
`;
initialStateScript = `${xarc}.dyn("${dataId}")`;
}
+ // about using safeStringifyJson here: We don't expect user to write their own code
+ // with in their options, but if they do, it's their problem,
+ // but we at least avoid code blowing up due to that.
this.outputSpot.add(
`
${ssrContent}${initialStateData}
-
`
);
diff --git a/packages/xarc-subapp/src/node/utils.ts b/packages/xarc-subapp/src/node/utils.ts
index 30f43fad4..1039aeac8 100644
--- a/packages/xarc-subapp/src/node/utils.ts
+++ b/packages/xarc-subapp/src/node/utils.ts
@@ -101,3 +101,14 @@ export function urlJoin(baseUrl: string, ...pathParts: string[]) {
}
export const SSR_PIPELINES = Symbol("subapp-ssr-pipelines");
+
+/**
+ * Stringify a JSON object and replace some tags to avoid XSS:
+ * - `` => `</script>`
+ * @param obj - object to stringify
+ * @returns JSON string of object
+ */
+export function safeStringifyJson(obj) {
+ return JSON.stringify(obj).replace(/<(\/?)script>/g, "<$1script>");
+}
diff --git a/samples/subapp2-poc/src/demo3-static-props.ts b/samples/subapp2-poc/src/demo3-static-props.ts
index eb7fc705a..a7aab124c 100644
--- a/samples/subapp2-poc/src/demo3-static-props.ts
+++ b/samples/subapp2-poc/src/demo3-static-props.ts
@@ -6,7 +6,13 @@ export const getStaticProps = async () => {
setTimeout(resolve, delay);
}).then(() => {
return {
- props: { message: "demo3 this is static props", delay }
+ props: {
+ message: "demo3 this is static props",
+ // this actually won't execute even if we didn't escape the ",
+ delay
+ }
};
});
};