Algernon: Host header path traversal in --domain mode reads files and runs Lua from parent dir
Date: 2026-06
Affected: go/github.com/xyproto/algernon
Attack vector: path-traversal
Category: vulnerability-disclosure
Severity: High
CVE: CVE-2026-48126
CWE: CWE-22
CWE-23
CWE-644
CVSS: 8.2 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N)

### Summary When algernon is started with `--domain` (or `--letsencrypt`, which silently turns on `--domain` at `engine/flags.go:372`), the request handler resolves the served directory by joining the configured `--dir` with the value of the client-supplied `Host` header. The join is performed by `filepath.Join` with no validation, so a `Host: ..` header walks one level above the document root. Subsequent file resolution then exposes everything in that parent directory — arbitrary file read, full directory listing, and, if any `.lua` file is present, server-side Lua execution. Algernon 1.17.7 and earlier are affected. ### Details `engine/handlers.go` (function `RegisterHandlers`, around line 510): ```go allRequests := func(w http.ResponseWriter, req *http.Request) { ... servedir := servedir if addDomain { servedir = filepath.Join(servedir, utils.GetDomain(req)) // <— line 531 } ... filename := utils.URL2filename(servedir, urlpath) ``` `utils/web.go` (`GetDomain`): ```go func GetDomain(req *http.Request) string { host, _, err := net.SplitHostPort(req.Host) if err != nil { return req.Host // <— Host header returned verbatim } return host } ``` `utils/files.go` (`URL2filename`) only sanitises the URL path — it never inspects `dirname`: ```go func URL2filename(dirname, urlpath string) string { if strings.Contains(urlpath, "..") { return dirname + Pathsep // dirname is trusted here }


Mitigations:

Architectural layers (MAESTRO):