From c6fcdaab6e8eb7f0259c5337b5fe6591f54eaef6 Mon Sep 17 00:00:00 2001 From: John Downey Date: Fri, 08 May 2026 21:43:35 +0000 Subject: [PATCH] Add path traversal protection to file serving Check for ".." segments before resolving the file path. --- README.md | 9 +++++++++ examples/src/serving_files.gleam | 9 +++++++++ 2 file(s) changed, 18 insertion(s)(+), 0 deletion(s)(-) diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -234,7 +234,9 @@ Static files can be sent using [`ewe.file`](https://hexdocs.pm/ewe/ewe.html#file). It accepts a path and optional `offset`/`limit` parameters. This allows serving HTML pages, assets, or binary files with minimal effort. ```gleam +import gleam/bool import gleam/http/response +import gleam/list import gleam/string fn serve_file(path: String) -> Response { @@ -243,6 +245,13 @@ // let dir = absname("public") let relative = string.drop_start(path, 1) + let segments = string.split(relative, "/") + + use <- bool.guard( + when: list.any(segments, fn(seg) { seg == ".." }), + return: not_found(), + ) + let resolved = absname_join(dir, relative) case string.starts_with(resolved, dir <> "/") { diff --git a/examples/src/serving_files.gleam b/examples/src/serving_files.gleam --- a/examples/src/serving_files.gleam +++ b/examples/src/serving_files.gleam @@ -1,6 +1,8 @@ import ewe.{type Response} +import gleam/bool import gleam/erlang/process import gleam/http/response +import gleam/list import gleam/option.{None} import gleam/string import logging @@ -26,6 +28,13 @@ // let dir = absname("public") let relative = string.drop_start(path, 1) + let segments = string.split(relative, "/") + + use <- bool.guard( + when: list.any(segments, fn(seg) { seg == ".." }), + return: not_found(), + ) + let resolved = absname_join(dir, relative) case string.starts_with(resolved, dir <> "/") { -- tangled.sh