Skip to content

Commit 9c3678e

Browse files
lihbrDriesOlbrechts
andcommitted
feat: enforce allowed message origin in non-development environment
Co-authored-by: DriesOlbrechts <driesolbrechts@gmail.com>
1 parent 6586afa commit 9c3678e

3 files changed

Lines changed: 90 additions & 11 deletions

File tree

src/channel/ChannelReceiver.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ export abstract class ChannelReceiver<
7979

8080
/** Handles public messages */
8181
private _onPublicMessage(event: MessageEvent<unknown>): void {
82+
if (process.env.NODE_ENV !== "development") {
83+
if (
84+
!event.origin.startsWith("https://") ||
85+
!(
86+
event.origin.endsWith(".prismic.io") ||
87+
event.origin.endsWith(".wroom.io") ||
88+
event.origin.endsWith(".marketing-tools-wroom.com") ||
89+
event.origin.endsWith(".dev-tools-wroom.com") ||
90+
event.origin.endsWith(".platform-wroom.com")
91+
)
92+
) {
93+
// Ignore messages from non-allowed origins in non-development environment
94+
return
95+
}
96+
}
97+
8298
try {
8399
const message = validateMessage(event.data)
84100

test/channel-ChannelReceiver-onPublicMessage.test.ts

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
class StandaloneChannelReceiver extends ChannelReceiver {}
1212

1313
const dummyData = { foo: "bar" }
14+
const trustedOrigin = "https://foo.prismic.io"
15+
1416

1517
it("gets wired to public message events on class instantiation", () => {
1618
const channelReceiver = new StandaloneChannelReceiver({}, {})
@@ -32,7 +34,7 @@ it("debug logs messages when on debug mode", (ctx) => {
3234
const request = createRequestMessage(ctx.task.name, dummyData)
3335

3436
// @ts-expect-error - taking a shortcut by accessing private property
35-
channelReceiver._onPublicMessage({ data: request })
37+
channelReceiver._onPublicMessage({ data: request, origin: trustedOrigin })
3638

3739
expect(
3840
console.debug,
@@ -49,7 +51,7 @@ it("doesn't debug log messages when not on debug mode", (ctx) => {
4951
const request = createRequestMessage(ctx.task.name, dummyData)
5052

5153
// @ts-expect-error - taking a shortcut by accessing private property
52-
channelReceiver._onPublicMessage({ data: request })
54+
channelReceiver._onPublicMessage({ data: request, origin: trustedOrigin })
5355

5456
expect(console.debug).not.toHaveBeenCalled()
5557
})
@@ -59,7 +61,7 @@ it("doens't throw on invalid message received", () => {
5961

6062
expect(() => {
6163
// @ts-expect-error - taking a shortcut by accessing private property
62-
channelReceiver._onPublicMessage({ data: null })
64+
channelReceiver._onPublicMessage({ data: null, origin: trustedOrigin })
6365
}).not.toThrowError()
6466
})
6567

@@ -77,7 +79,7 @@ it("throws on other errors", (ctx) => {
7779

7880
expect(() => {
7981
// @ts-expect-error - taking a shortcut by accessing private property
80-
channelReceiver._onPublicMessage({ data: request })
82+
channelReceiver._onPublicMessage({ data: request, origin: trustedOrigin })
8183
}).toThrowError(ctx.task.name)
8284

8385
vi.restoreAllMocks()
@@ -94,7 +96,7 @@ it("accepts connect requests", () => {
9496
const response = createSuccessResponseMessage(request.requestID, undefined)
9597

9698
// @ts-expect-error - taking a shortcut by accessing private property
97-
channelReceiver._onPublicMessage({ data: request, ports: [channel.port1] })
99+
channelReceiver._onPublicMessage({ data: request, ports: [channel.port1], origin: trustedOrigin })
98100

99101
expect(postResponseStub).toHaveBeenCalledOnce()
100102
expect(postResponseStub).toHaveBeenCalledWith(response)
@@ -112,7 +114,7 @@ it("updates its options following connect request", () => {
112114
expect(channelReceiver.options.foo).toBeUndefined()
113115

114116
// @ts-expect-error - taking a shortcut by accessing private property
115-
channelReceiver._onPublicMessage({ data: request, ports: [channel.port1] })
117+
channelReceiver._onPublicMessage({ data: request, ports: [channel.port1], origin: trustedOrigin })
116118

117119
expect(channelReceiver.options.foo).toBe("bar")
118120
})
@@ -126,7 +128,7 @@ it("rejects non-connect requests", (ctx) => {
126128
const response = createErrorResponseMessage(request.requestID, undefined)
127129

128130
// @ts-expect-error - taking a shortcut by accessing private property
129-
channelReceiver._onPublicMessage({ data: request })
131+
channelReceiver._onPublicMessage({ data: request, origin: trustedOrigin })
130132

131133
expect(postResponseStub).toHaveBeenCalledOnce()
132134
// @ts-expect-error - type is broken
@@ -143,10 +145,10 @@ it("forwards response messages to default message handler when not ready", (ctx)
143145
const response = createSuccessResponseMessage(ctx.task.name, undefined)
144146

145147
// @ts-expect-error - taking a shortcut by accessing private property
146-
channelReceiver._onPublicMessage({ data: response })
148+
channelReceiver._onPublicMessage({ data: response, origin: trustedOrigin })
147149

148150
expect(onMessageStub).toHaveBeenCalledOnce()
149-
expect(onMessageStub).toHaveBeenCalledWith({ data: response })
151+
expect(onMessageStub).toHaveBeenCalledWith({ data: response, origin: trustedOrigin })
150152

151153
vi.restoreAllMocks()
152154
})
@@ -161,7 +163,66 @@ it("doesn't forward response messages to default message handler once ready", (c
161163
const response = createSuccessResponseMessage(ctx.task.name, undefined)
162164

163165
// @ts-expect-error - taking a shortcut by accessing private property
164-
channelReceiver._onPublicMessage({ data: response })
166+
channelReceiver._onPublicMessage({ data: response, origin: trustedOrigin })
167+
168+
expect(onMessageStub).not.toHaveBeenCalled()
169+
})
170+
171+
it("accepts any origin in development", (ctx) => {
172+
const nodeEnv = process.env.NODE_ENV
173+
process.env.NODE_ENV = "development"
174+
175+
const channelReceiver = new StandaloneChannelReceiver({}, {})
176+
// @ts-expect-error - taking a shortcut by accessing protected property
177+
const onMessageStub = vi.spyOn(channelReceiver, "onMessage")
178+
179+
const response = createSuccessResponseMessage(ctx.task.name, undefined)
180+
181+
try {
182+
// @ts-expect-error - taking a shortcut by accessing private property
183+
channelReceiver._onPublicMessage({
184+
data: response,
185+
origin: "http://foo.example.com",
186+
})
187+
} finally {
188+
process.env.NODE_ENV = nodeEnv
189+
}
190+
191+
expect(onMessageStub).toHaveBeenCalledOnce()
192+
expect(onMessageStub).toHaveBeenCalledWith({
193+
data: response,
194+
origin: "http://foo.example.com",
195+
})
196+
})
197+
198+
it("rejects non-https origins outside development", (ctx) => {
199+
const channelReceiver = new StandaloneChannelReceiver({}, {})
200+
// @ts-expect-error - taking a shortcut by accessing protected property
201+
const onMessageStub = vi.spyOn(channelReceiver, "onMessage")
202+
203+
const response = createSuccessResponseMessage(ctx.task.name, undefined)
204+
205+
// @ts-expect-error - taking a shortcut by accessing private property
206+
channelReceiver._onPublicMessage({
207+
data: response,
208+
origin: "http://foo.prismic.io",
209+
})
210+
211+
expect(onMessageStub).not.toHaveBeenCalled()
212+
})
213+
214+
it("rejects non-prismic origins outside development", (ctx) => {
215+
const channelReceiver = new StandaloneChannelReceiver({}, {})
216+
// @ts-expect-error - taking a shortcut by accessing protected property
217+
const onMessageStub = vi.spyOn(channelReceiver, "onMessage")
218+
219+
const response = createSuccessResponseMessage(ctx.task.name, undefined)
220+
221+
// @ts-expect-error - taking a shortcut by accessing private property
222+
channelReceiver._onPublicMessage({
223+
data: response,
224+
origin: "https://foo.example.com",
225+
})
165226

166227
expect(onMessageStub).not.toHaveBeenCalled()
167228
})

test/channel-ChannelReceiver-ready.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ChannelReceiver, createSuccessResponseMessage } from "../src/channel"
55

66
class StandaloneChannelReceiver extends ChannelReceiver {}
77

8+
const trustedOrigin = "https://foo.prismic.io"
9+
810
it("throws when not embedded as an iframe", async () => {
911
const channelReceiver = new StandaloneChannelReceiver({}, {})
1012

@@ -24,7 +26,7 @@ it("sends ready request when embedded as an iframe", async (ctx) => {
2426
const postMessageMock = vi.fn((request: UnknownRequestMessage) => {
2527
response = createSuccessResponseMessage(request.requestID, undefined)
2628
// @ts-expect-error - taking a shortcut by accessing private property
27-
channelReceiver._onPublicMessage({ data: response })
29+
channelReceiver._onPublicMessage({ data: response, origin: trustedOrigin })
2830
})
2931
window.parent = {
3032
postMessage: postMessageMock as Window["postMessage"],

0 commit comments

Comments
 (0)