Skip to content

Commit a9e3233

Browse files
committed
Fix security issue
WebSocket messages should not be processed until `initially` has finished and has a chance to optionally close the connection completely. Disconnects and protocol-level errors are processed regardless - these occur at a lower level and should be preserved for consistency with connects.
1 parent cbf31ca commit a9e3233

File tree

1 file changed

+17
-1
lines changed

1 file changed

+17
-1
lines changed

hapi-plugin-websocket.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ const register = async (server, pluginOptions) => {
215215
delete headers["accept-encoding"]
216216

217217
/* optionally inject an empty initial message */
218+
let initially;
218219
if (routeOptions.initially) {
220+
let resolveInitially;
221+
initially = new Promise(resolve => resolveInitially = resolve);
219222
/* inject incoming WebSocket message as a simulated HTTP request */
220223
server.inject({
221224
/* simulate the hard-coded POST request */
@@ -236,7 +239,10 @@ const register = async (server, pluginOptions) => {
236239
}).then(response => {
237240
/* any HTTP redirection, client error or server error response
238241
leads to an immediate WebSocket connection drop */
239-
if (response.statusCode >= 300) {
242+
if (response.statusCode < 300) {
243+
resolveInitially(true);
244+
} else {
245+
resolveInitially(false);
240246
const annotation = `(HAPI handler responded with HTTP status ${response.statusCode})`
241247
if (response.statusCode < 400)
242248
ws.close(1002, `Protocol Error ${annotation}`)
@@ -252,6 +258,10 @@ const register = async (server, pluginOptions) => {
252258
if (routeOptions.frame === true) {
253259
/* framed WebSocket communication (correlated request/reply) */
254260
wsf.on("message", async (ev) => {
261+
if (initially && !(await initially)) {
262+
return;
263+
}
264+
255265
/* allow application to hook into raw WebSocket frame processing */
256266
routeOptions.frameMessage.call(ctx, { ctx, wss, ws, wsf, req, peers }, ev.frame)
257267

@@ -294,6 +304,10 @@ const register = async (server, pluginOptions) => {
294304
else {
295305
/* plain WebSocket communication (uncorrelated request/response) */
296306
ws.on("message", async (message) => {
307+
if (initially && !(await initially)) {
308+
return;
309+
}
310+
297311
/* inject incoming WebSocket message as a simulated HTTP request */
298312
const response = await server.inject({
299313
/* simulate the hard-coded POST request */
@@ -322,6 +336,7 @@ const register = async (server, pluginOptions) => {
322336
/* hook into WebSocket disconnection */
323337
ws.on("close", () => {
324338
/* allow application to hook into WebSocket disconnection */
339+
/* note that this is done even if the `initially` handler closes the connection */
325340
routeOptions.disconnect.call(ctx, { ctx, wss, ws, wsf, req, peers })
326341

327342
/* stop tracking the peer */
@@ -330,6 +345,7 @@ const register = async (server, pluginOptions) => {
330345
})
331346

332347
/* allow application to hook into WebSocket error processing */
348+
/* note that this is done even if the `initially` handler closes the connection */
333349
ws.on("error", (error) => {
334350
routeOptions.error.call(ctx, { ctx, wss, ws, wsf, req, peers }, error)
335351
})

0 commit comments

Comments
 (0)