Skip to content

Commit 8ccd3e6

Browse files
committed
Enforce allowed iframe message origins and use type field to identify messages
1 parent b7ce17e commit 8ccd3e6

4 files changed

Lines changed: 39 additions & 20 deletions

File tree

src/components/Editor/Runners/HtmlRunner/HtmlRenderer.jsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import {
77
matchingRegexes,
88
} from "../../../../utils/externalLinkHelper";
99
import "../../../../assets/stylesheets/HtmlRunner.scss";
10+
import {
11+
allowedIframeHost,
12+
MSG_HTML_PREVIEW_EVENT,
13+
MSG_HTML_PREVIEW_READY,
14+
MSG_HTML_PROJECT_UPDATE,
15+
} from "../../../../utils/iframeUtils";
1016

1117
const parentTag = (node, tag) =>
1218
node.parentNode?.tagName && node.parentNode.tagName.toLowerCase() === tag;
@@ -55,7 +61,7 @@ const replaceHrefNodes = (indexPage, projectMedia, projectCode) => {
5561
} else {
5662
// eslint-disable-next-line no-script-url
5763
hrefNode.setAttribute("href", "javascript:void(0)");
58-
onClick = `window.parent.postMessage({msg: 'RELOAD', payload: { linkTo: '${projectFile.name}' }})`;
64+
onClick = `window.parent.postMessage({type: '${MSG_HTML_PREVIEW_EVENT}', msg: 'RELOAD', payload: { linkTo: '${projectFile.name}' }})`;
5965
}
6066
} else {
6167
const matchingExternalHref = matchingRegexes(
@@ -73,9 +79,9 @@ const replaceHrefNodes = (indexPage, projectMedia, projectCode) => {
7379
) {
7480
// eslint-disable-next-line no-script-url
7581
hrefNode.setAttribute("href", "javascript:void(0)");
76-
onClick = "window.parent.postMessage({msg: 'ERROR: External link'})";
82+
onClick = `window.parent.postMessage({type: '${MSG_HTML_PREVIEW_EVENT}', msg: 'ERROR: External link'})`;
7783
} else if (matchingExternalHref) {
78-
onClick = `window.parent.postMessage({msg: 'Allowed external link', payload: { linkTo: '${hrefNode.attrs.href}' }})`;
84+
onClick = `window.parent.postMessage({type: '${MSG_HTML_PREVIEW_EVENT}', msg: 'Allowed external link', payload: { linkTo: '${hrefNode.attrs.href}' }})`;
7985
}
8086
}
8187

@@ -125,10 +131,15 @@ export function HtmlRenderer() {
125131

126132
const handlePreviewUpdateFromHost = useCallback(
127133
(event) => {
128-
// todo: validate message origin
129-
// todo: use "type" to check what kind of message this is
134+
if (!allowedIframeHost(event.origin)) {
135+
console.warn(
136+
"iFrame received message from unknown origin:",
137+
event.origin,
138+
);
139+
return;
140+
}
130141
const message = event.data;
131-
if (message?.current) {
142+
if (message?.type === MSG_HTML_PROJECT_UPDATE && message?.current) {
132143
const transformedHtml = parse(message.current);
133144

134145
replaceHrefNodes(transformedHtml, message.media, message.code);
@@ -149,8 +160,7 @@ export function HtmlRenderer() {
149160
const handleEventFromPreview = (event) => {
150161
// todo: validate message origin
151162
const message = event.data;
152-
// todo: use "type" to check what kind of message this is
153-
if (typeof event.data?.msg === "string") {
163+
if (message?.type === MSG_HTML_PREVIEW_EVENT) {
154164
// Forward events originating from the previewed code back to the host
155165
// todo: set appropriate target origin
156166
window.parent.postMessage(message, "*");
@@ -164,7 +174,7 @@ export function HtmlRenderer() {
164174
const source = window.opener || window.parent;
165175
if (source) {
166176
// todo: set appropriate target origin
167-
source.postMessage({ ready: true }, "*");
177+
source.postMessage({ type: MSG_HTML_PREVIEW_READY }, "*");
168178
}
169179
return () => {
170180
window.removeEventListener("message", handlePreviewUpdateFromHost);

src/components/Editor/Runners/HtmlRunner/HtmlRunner.jsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
2121
import OpenInNewTabIcon from "../../../../assets/icons/open_in_new_tab.svg";
2222
import RunnerControls from "../../../RunButton/RunnerControls";
2323
import { MOBILE_MEDIA_QUERY } from "../../../../utils/mediaQueryBreakpoints";
24+
import {
25+
MSG_HTML_PREVIEW_EVENT,
26+
MSG_HTML_PREVIEW_READY,
27+
MSG_HTML_PROJECT_UPDATE,
28+
} from "../../../../utils/iframeUtils";
2429

2530
function HtmlRunner() {
2631
const project = useSelector((state) => state.editor.project);
@@ -109,7 +114,7 @@ function HtmlRunner() {
109114

110115
const eventListener = () => {
111116
window.addEventListener("message", (event) => {
112-
if (typeof event.data?.msg === "string") {
117+
if (event.data?.type === MSG_HTML_PREVIEW_EVENT) {
113118
if (event.data?.msg === "ERROR: External link") {
114119
handleExternalLinkError(showModal);
115120
} else if (event.data?.msg === "Allowed external link") {
@@ -211,7 +216,7 @@ function HtmlRunner() {
211216
(event) => {
212217
const message = event.data;
213218
// todo: validate message source
214-
if (message.ready === true) {
219+
if (message?.type === MSG_HTML_PREVIEW_READY) {
215220
setRendererReady(true);
216221
}
217222
},
@@ -228,7 +233,7 @@ function HtmlRunner() {
228233

229234
output.current.contentWindow.postMessage(
230235
{
231-
type: "editor-html-preview",
236+
type: MSG_HTML_PROJECT_UPDATE,
232237
code: projectCode,
233238
media: projectMedia,
234239
current: indexPage.toString(),

src/components/ScratchEditor/ScratchIntegrationHOC.jsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
manualUpdateProject,
88
setStageSize,
99
} from "@scratch/scratch-gui";
10+
import { allowedIframeHost } from "../../utils/iframeUtils";
1011

1112
const ScratchIntegrationHOC = function (WrappedComponent) {
1213
class ScratchIntegrationComponent extends React.Component {
@@ -27,15 +28,8 @@ const ScratchIntegrationHOC = function (WrappedComponent) {
2728
window.removeEventListener("message", this.handleMessage);
2829
}
2930

30-
allowedIframeHost(origin) {
31-
const allowedHosts = process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS
32-
? process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS.split(",")
33-
: [];
34-
return allowedHosts.includes(origin);
35-
}
36-
3731
handleMessage(event) {
38-
if (!this.allowedIframeHost(event.origin)) {
32+
if (!allowedIframeHost(event.origin)) {
3933
console.warn(
4034
"iFrame received message from unknown origin:",
4135
event.origin,

src/utils/iframeUtils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export function allowedIframeHost(origin) {
2+
const allowedHosts = process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS
3+
? process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS.split(",")
4+
: [];
5+
return allowedHosts.includes(origin);
6+
}
7+
8+
export const MSG_HTML_PREVIEW_READY = "editor-html-ready";
9+
export const MSG_HTML_PROJECT_UPDATE = "editor-html-preview";
10+
export const MSG_HTML_PREVIEW_EVENT = "editor-html-event";

0 commit comments

Comments
 (0)