import { describe, expect, it } from "vitest"; import { serializeRuntimeConfig } from "./runtime-config-serialize"; // Pin a security-critical path: the inline-injected runtime config // lands inside a block in the root layout. A // hostile env value containing the closing-script substring would // otherwise break out and let the attacker inject arbitrary HTML. // JSON.stringify does NOT escape `<` by default, so we MUST escape // it to `<` here. U+2028 / U+2029 must also be escaped because // the JS string literal parser treats them as line terminators in // older engines. // // Tokenizer note: we build the test inputs via String.fromCharCode // so the literal U+2028 / U+2029 codepoints never appear in this // source file (some editors / build pipelines reject them). const LS = String.fromCharCode(0x2028); const PS = String.fromCharCode(0x2029); describe("serializeRuntimeConfig (shell-docs)", () => { it("escapes `<` so the closing-script substring cannot appear in the output", () => { const malicious = { baseUrl: "" }; const serialized = serializeRuntimeConfig(malicious); expect(serialized).not.toContain(""); expect(serialized).toContain("\\u003c"); }); it("escapes U+2028 (LINE SEPARATOR) to \\u2028", () => { const cfg = { baseUrl: `before${LS}after` }; const serialized = serializeRuntimeConfig(cfg); expect(serialized).not.toContain(LS); expect(serialized).toContain("\\u2028"); }); it("escapes U+2029 (PARAGRAPH SEPARATOR) to \\u2029", () => { const cfg = { baseUrl: `before${PS}after` }; const serialized = serializeRuntimeConfig(cfg); expect(serialized).not.toContain(PS); expect(serialized).toContain("\\u2029"); }); it("round-trips a normal config via JSON.parse", () => { const cfg = { baseUrl: "https://docs.example.com", shellUrl: "https://shell.example.com", posthogKey: "phc_real", }; const serialized = serializeRuntimeConfig(cfg); // The browser receives `window.__SHOWCASE_CONFIG__=;` // and evaluates it. JSON.parse accepts the escaped output // identically (the JS engine resolves < / / ). expect(JSON.parse(serialized)).toEqual(cfg); }); it("does not double-escape already-safe characters", () => { const cfg = { baseUrl: "https://docs.copilotkit.ai" }; const serialized = serializeRuntimeConfig(cfg); expect(serialized).toBe(JSON.stringify(cfg)); }); });