Skip to content

Commit 09cee03

Browse files
authored
Merge pull request #1606 from avinxshKD/fix/codeembed-postmessage-origin
Restrict CodeFrame postMessage origin
2 parents 7c6ba30 + 729b7fa commit 09cee03

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

‎src/components/CodeEmbed/frame.tsx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface CodeBundle {
77
js?: string;
88
base?: string;
99
scripts?: string[];
10+
parentOrigin: string;
1011
}
1112

1213
/*
@@ -26,7 +27,7 @@ const wrapSketch = (sketchCode?: string) => {
2627

2728
/*
2829
* Wraps the given code in a html document for display.
29-
* Single object argument, all properties optional:
30+
* Single object argument:
3031
*/
3132
const wrapInMarkup = (code: CodeBundle) =>
3233
`<!DOCTYPE html>
@@ -51,6 +52,8 @@ ${(code.scripts?.length ?? 0) > 0 ? '' : `
5152
<script type="text/javascript">
5253
// Listen for p5.min.js text content and include in iframe's head as script
5354
window.addEventListener("message", event => {
55+
if (event.origin !== '${code.parentOrigin}') return;
56+
if (event.source !== window.parent) return;
5457
// Include check to prevent p5.min.js from being loaded twice
5558
const scriptExists = !!document.getElementById("p5ScriptTagInIframe");
5659
if (!scriptExists && event.data?.sender === '${cdnLibraryUrl}') {
@@ -139,7 +142,7 @@ export const CodeFrame = (props: CodeFrameProps) => {
139142
sender: cdnLibraryUrl,
140143
message: p5ScriptText,
141144
},
142-
"*",
145+
window.location.origin,
143146
);
144147
} catch (e) {
145148
console.error(`Error loading ${p5ScriptTag.src}`);
@@ -161,6 +164,7 @@ export const CodeFrame = (props: CodeFrameProps) => {
161164
htmlBody: props.htmlBodyCode,
162165
base: props.base,
163166
scripts: props.scripts,
167+
parentOrigin: window.location.origin,
164168
}) : ""}
165169
sandbox="allow-scripts allow-popups allow-modals allow-forms allow-same-origin"
166170
aria-label="Code Preview"

‎test/components/CodeFrame.test.tsx‎

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
import { it, expect, suite, assert } from "vitest";
2-
import { render, screen, act, cleanup, within } from "@testing-library/preact";
1+
import { it, expect, suite, assert, vi } from "vitest";
2+
import {
3+
render,
4+
screen,
5+
act,
6+
cleanup,
7+
waitFor,
8+
within,
9+
} from "@testing-library/preact";
310
import { CodeFrame } from "@components/CodeEmbed/frame";
11+
import { cdnLibraryUrl } from "@/src/globals/globals";
412

513
suite("CodeFrame", () => {
614
it("defaults to empty DOM", async () => {
@@ -30,4 +38,65 @@ suite("CodeFrame", () => {
3038

3139
cleanup();
3240
});
41+
42+
it("only sends the p5 script to its own iframe origin", async () => {
43+
const postMessage = vi.fn();
44+
const contentWindowSpy = vi
45+
.spyOn(HTMLIFrameElement.prototype, "contentWindow", "get")
46+
.mockReturnValue({ postMessage } as unknown as Window);
47+
class ImmediateIntersectionObserver {
48+
private callback: IntersectionObserverCallback;
49+
50+
constructor(callback: IntersectionObserverCallback) {
51+
this.callback = callback;
52+
}
53+
54+
observe() {
55+
this.callback(
56+
[{ isIntersecting: true } as IntersectionObserverEntry],
57+
this as unknown as IntersectionObserver,
58+
);
59+
}
60+
61+
disconnect() {}
62+
}
63+
vi.stubGlobal("IntersectionObserver", ImmediateIntersectionObserver);
64+
vi.stubGlobal(
65+
"fetch",
66+
vi.fn().mockResolvedValue({ text: () => Promise.resolve("p5 source") }),
67+
);
68+
const p5Script = document.createElement("script");
69+
p5Script.id = "p5ScriptTag";
70+
p5Script.src = "/p5.min.js";
71+
document.body.appendChild(p5Script);
72+
73+
try {
74+
act(() => {
75+
render(<CodeFrame jsCode="function setup() {}" />);
76+
});
77+
78+
const frame = screen.getByTitle("Code Preview") as HTMLIFrameElement;
79+
80+
await waitFor(() =>
81+
expect(postMessage).toHaveBeenCalledWith(
82+
{
83+
sender: cdnLibraryUrl,
84+
message: "p5 source",
85+
},
86+
window.location.origin,
87+
),
88+
);
89+
expect(frame.srcdoc).toContain(
90+
`if (event.origin !== '${window.location.origin}') return;`,
91+
);
92+
expect(frame.srcdoc).toContain(
93+
"if (event.source !== window.parent) return;",
94+
);
95+
} finally {
96+
cleanup();
97+
p5Script.remove();
98+
contentWindowSpy.mockRestore();
99+
vi.unstubAllGlobals();
100+
}
101+
});
33102
});

0 commit comments

Comments
 (0)