|
| 1 | +*Author: [Dredsen](https://dredsen.github.io/)* |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +Algernon is a small, self-contained web server written in Go. You point it at a directory and it serves whatever's there: Markdown, Lua, templates, static files, the lot. The pitch is "drop a single binary in a folder and you've got a working site." It's the kind of tool people reach for when they want zero-setup file serving without standing up nginx or a framework and just want something thats simple and works out of the box. |
| 6 | + |
| 7 | +I decided to test out using AI Fuzzing with Qwen 3.6 35B with a custom harness on the main branch of Algernon 1.17.6 and ended up with four advisories. None of them require authentication, all of them work on a default install with no flags, and three of them are reachable just by running the documented quickstart inside a project folder. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## What I found |
| 12 | + |
| 13 | +### 1. `handler.lua` discovery walks above the server root |
| 14 | + |
| 15 | +When Algernon serves a directory, it looks for a `handler.lua` script to use as the request handler. If the current directory doesn't have one, the search walks **upward** through parent directories, past the configured server root, all the way toward the filesystem root. |
| 16 | + |
| 17 | +The first `handler.lua` it finds gets loaded into the Lua interpreter with the full Algernon API exposed - file system, shell, HTTP client, database drivers. Anyone who can write a file at any ancestor path on disk gets pre-authenticated code execution on the next request. |
| 18 | + |
| 19 | +Plant a `handler.lua` one directory above the served root and any unauthenticated request triggers it: |
| 20 | + |
| 21 | +``` |
| 22 | +$ ./algernon site/ |
| 23 | +$ curl http://127.0.0.1:8080/ |
| 24 | +=== PWNED via parent handler.lua === |
| 25 | +Hostname: DESKTOP-4RLE5YR |
| 26 | +``` |
| 27 | + |
| 28 | +The handler that lives one directory **above** `site/` (and was never part of the served tree) executed inside the Algernon process and its output became the HTTP 200 response body. |
| 29 | + |
| 30 | +### 2. Auto-refresh SSE listener leaks edits across the network |
| 31 | + |
| 32 | +The `-a` / `--autorefresh` flag spins up a second HTTP listener that streams a Server-Sent-Events feed of every filename the developer is editing. That listener: |
| 33 | + |
| 34 | +- binds to all interfaces by default on Linux and macOS, |
| 35 | +- sets `Access-Control-Allow-Origin: *` on every response, |
| 36 | +- has no authentication and is not gated by the main permission system. |
| 37 | + |
| 38 | +Anything on the same network can subscribe to a live transcript of your editor activity. Any web page you open in a browser can subscribe cross-origin. The leak is filenames and timing, not file contents, but it's still a real-time map of your project layout to anyone who asks. |
| 39 | + |
| 40 | +``` |
| 41 | +$ curl -H "Origin: http://evil.example" http://127.0.0.1:5553/sse |
| 42 | +HTTP/1.1 200 OK |
| 43 | +Access-Control-Allow-Origin: * |
| 44 | +Content-Type: text/event-stream |
| 45 | +
|
| 46 | +id: 0 |
| 47 | +data: C:\Users\xbox\...\site\.env.local |
| 48 | +``` |
| 49 | + |
| 50 | +The stream emits **absolute paths**, so it also leaks the developer's username, drive letter, and directory layout. No `Authorization`, no cookie, no token, and the `Origin: http://evil.example` header was happily reflected as a wildcard. |
| 51 | + |
| 52 | +### 3. Default install serves dotfiles, including `.git` and `.env` |
| 53 | + |
| 54 | +The documented quickstart is `algernon .` - point it at the current directory. If that directory is a git repository, the server happily serves `.git/config`, `.git/HEAD`, pack files, anything else under `.git/`. Same for `.env`, `.ssh`, `.htpasswd`, anything starting with a dot. |
| 55 | + |
| 56 | +There's an opt-in `ignore.txt` mechanism, but it only hides files from the directory listing - the file handler itself still serves them when the URL is requested directly. So patterns in `ignore.txt` cover one half of the exposure and leave the other half wide open. |
| 57 | + |
| 58 | +``` |
| 59 | +$ curl http://127.0.0.1:8080/.env |
| 60 | +DATABASE_URL=postgres://app:hunter2@db.internal:5432/prod |
| 61 | +SECRET_KEY_BASE=fake-jwt-signing-secret-for-poc-only |
| 62 | +
|
| 63 | +$ curl http://127.0.0.1:8080/.git/config |
| 64 | +[core] |
| 65 | + repositoryformatversion = 0 |
| 66 | +[remote "origin"] |
| 67 | + url = git@github.com:internal/private-repo.git |
| 68 | +``` |
| 69 | + |
| 70 | +Both came back unauthenticated. The `.env` was on the `ignore.txt` block-list and still served - the listing hid it, the file handler didn't care. |
| 71 | + |
| 72 | +### 4. Single-file mode forces debug mode |
| 73 | + |
| 74 | +Algernon has a "single file" shortcut: `algernon foo.po2` or `algernon page.html` to demo one file without setting up a directory. This mode unconditionally sets `debugMode = true`, no opt-out. |
| 75 | + |
| 76 | +`debugMode` turns on the pretty error page, which on any script or template error dumps the full server-side source of the file being rendered, line-by-line, with the absolute filesystem path. If the served file contains secrets (API keys hardcoded into a Lua script, database credentials in a template), a single malformed request that triggers an error is enough to leak them. |
| 77 | + |
| 78 | +``` |
| 79 | +$ ./algernon page.po2 # page.po2 references data.lua, which has a parse error |
| 80 | +$ curl http://127.0.0.1:8080/ |
| 81 | +<title>Lua Error</title> |
| 82 | +... |
| 83 | +Contents of data.lua: |
| 84 | +local SECRET = "sk-LEAKCANARY-DATALUA-PRIVATE" |
| 85 | +this is intentionally bad lua |
| 86 | +``` |
| 87 | + |
| 88 | +The `SECRET` from `data.lua` ended up in the HTML response of an unauthenticated `GET /`. No debug flag was passed - single-file mode turned it on by itself. |
| 89 | + |
| 90 | +--- |
| 91 | +## Disclosure |
| 92 | + |
| 93 | +All four were reported privately to the maintainer and patched within 24 hours. Thanks to [xyproto](https://github.com/xyproto) for the quick turnaround, [Here are the in-depth reports for CVE/GHSA](https://github.com/xyproto/algernon/security). |
0 commit comments