Skip to content

Cross-origin unauthenticated WebSocket DoS: no Origin check + unhandled JSON.parse crashes the server #151

Description

Summary

The live-reload WebSocket server accepts connections from any origin (no Origin header validation during the handshake), and its message handler calls JSON.parse() on incoming frames with no try/catch. Any web page — including a malicious third-party site a developer merely has open in another browser tab while running eleventy --serve — can open a cross-origin WebSocket to the dev server and send one non-JSON frame to crash the entire process.

CWE: CWE-346 (Origin Validation Error) + CWE-248 (Uncaught Exception)
Severity: High
CVSS: 8.1 — CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:N/I:N/A:H

Root Cause

server.js (setupReloadNotifier's WebSocket connection handler), around line 1002-1003:

ws.on("message", (data) => {
  let parsed = JSON.parse(data.toString());  // no try/catch
  ...

updateServer.on("connection", ...) never inspects the handshake Origin header before accepting the WebSocket connection, so the classic "malicious webpage attacks your localhost dev server" pattern (the same class of bug behind several historical webpack-dev-server DNS-rebinding/cross-origin advisories) applies here directly.

Reproduction

With the dev server running (see companion issue #150 for setup), from a separate script simulating a cross-origin page:

const ws = new WebSocket("ws://localhost:PORT", { headers: { Origin: "http://evil.example.com" } });
ws.on("open", () => ws.send("NOT-VALID-JSON{{{"));

Server log:

SyntaxError: Unexpected token 'N', "NOT-VALID-JSON{{{" is not valid JSON
    at server.js:1003:27
Node.js v22.23.2   [process exits]

Verified: the connection was accepted despite the spoofed Origin header, and the server was confirmed alive before the frame and dead after.

Impact

Any web page a developer has open — completely unrelated to the site being built — can crash their local eleventy --serve session at will, with zero warning, just by the developer having that page open in another tab. This also means a malicious ad, compromised third-party script, or any attacker-controlled page can deny service to a developer's workflow.

Recommended Fix

  1. Validate the Origin header during the WebSocket upgrade/handshake, accepting only null/expected local origins (or use the verifyClient option most WS libraries provide).
  2. Wrap JSON.parse() in a try/catch and simply ignore/close the connection on malformed input instead of letting the exception propagate.

Verification

Dynamically confirmed on v3.0.0-alpha.11 against a real running server instance with a real cross-origin WebSocket client, as shown above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions