11ty / 11ty/dev-server

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

Open Beginner friendly
#150 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
109
Forks
20
Avg merge
1m
Merged PRs (30d)
1

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in server.js at getOutputDirFilePath() around line 317 and follow the request path through onRequestHandler() around line 854. Run the provided public-API reproduction with curl, then verify that the traversal request receives a normal 400/403 response and a later request still succeeds without terminating the process.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
backend, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.