GHSA-GJ84-924C-48FX
Vulnerability from github – Published: 2026-05-20 15:33 – Updated: 2026-05-20 15:33Summary
The SSE event server bound to 0.0.0.0:5553 on Linux/macOS by default because the platform-dependent host default in engine/flags.go:39-46 set host = "" for non-Windows, and utils.JoinHostPort("", ":5553") resolves to ":5553" — a Go http.Server.Addr of ":5553" listens on every interface. On Windows the same code chose "localhost", binding loopback only.
The result was a platform split where the OS Algernon's dev workflow is most often used on (Linux/macOS) got the network-exposed default, and only Windows users got the loopback-safe one. A LAN peer with no developer interaction could connect to <dev-laptop-ip>:5553 and read the file-change stream.
This advisory covers the bind-address default in isolation. The fix is independent of authentication (#2a) and CORS (#2b) — switching the default to loopback can be done without touching either.
Details
Root cause — platform-dependent host default in handleFlags
// engine/flags.go:39-46 (1.17.6)
host := ""
if runtime.GOOS == "windows" {
host = "localhost"
// Default Bolt database file
ac.defaultBoltFilename = filepath.Join(serverTempDir, "algernon.db")
// Default log file
ac.defaultLogFile = filepath.Join(serverTempDir, "algernon.log")
}
// engine/config.go:388-391 (1.17.6, finalConfiguration)
if ac.eventAddr == "" {
ac.eventAddr = utils.JoinHostPort(host, ac.defaultEventColonPort)
}
Result tabulated:
| Platform | host |
eventAddr after JoinHostPort |
Effective bind |
|---|---|---|---|
| Linux | "" |
":5553" |
0.0.0.0:5553 (all interfaces) |
| macOS | "" |
":5553" |
0.0.0.0:5553 (all interfaces) |
| Windows | "localhost" |
"localhost:5553" |
127.0.0.1:5553 (loopback) |
The same host value also governs the main web server bind, so the platform split affects both ports. The web-server bind on Linux/macOS is a separate (defensible) design decision — a server intended to be reachable; the SSE port is not such a service and inherited the same default by accident.
Why this is an independent finding
The fix is a single line: change the default host value, or change the eventAddr default specifically, to "localhost" regardless of platform. No change to authentication or CORS is required to close the network-reach half of the original bundled advisory. A LAN peer can no longer connect — the listener is unreachable from another host — even if the SSE handler still has no authentication and still returns Allow-Origin: *.
PoC (against 1.17.6 on Linux/macOS)
# Operator's laptop on a hotel/cafe/office WiFi:
algernon -a /path/to/project
# => SSE listener bound to 0.0.0.0:5553
# Any peer on the same subnet:
$ curl -sN http://<dev-laptop-ip>:5553/sse
id: 0
data: /path/to/project/secret-notes.md
id: 1
data: /path/to/project/.env.local
No interaction from the developer is required. The peer needs network reach and nothing else.
Impact
- Confidentiality: medium. LAN-bounded continuous information disclosure of filenames and edit timing.
- Integrity: none.
- Availability: none directly.
The CVSS vector uses AV:A (adjacent network) to model the LAN-only reach. The vector for a misconfigured deployment behind a NAT-less or routed network would shift to AV:N and rise to 5.3.
Suggestions to fix
Primary fix — pick localhost as the SSE default on every platform.
// engine/flags.go -- platform-independent default for the event listener
// (keep the existing platform split for the WEB server if desired, but
// not for the event server)
host := "localhost"
Or, more surgically:
// engine/config.go -- finalConfiguration
if ac.eventAddr == "" {
ac.eventAddr = utils.JoinHostPort("localhost", ac.defaultEventColonPort)
}
An operator who genuinely wants LAN-reachable SSE can pass --eventserver 0.0.0.0:5553 explicitly and accept the consequences.
Stronger fix — eliminate the second listener entirely. Mount the SSE handler on the main mux at /sse. The bind address is then whatever the main server uses; there is no second listener and therefore no second bind-address default to get wrong.
Live verification
Audit-host bind check (Windows 10):
$ netstat -an | findstr 5553
TCP 127.0.0.1:5553 0.0.0.0:0 LISTENING
Confirms the Windows default is localhost. The Linux/macOS bind to 0.0.0.0:5553 is documented in the code path above; it was not exercised on the audit machine because the audit host was Windows. A maintainer reproducing on a Linux host would see 0.0.0.0:5553 LISTENING from ss -tlnp.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.17.6"
},
"package": {
"ecosystem": "Go",
"name": "github.com/xyproto/algernon"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.17.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-46430"
],
"database_specific": {
"cwe_ids": [
"CWE-1188",
"CWE-668"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-20T15:33:56Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\n\nThe SSE event server bound to `0.0.0.0:5553` on Linux/macOS by default because the platform-dependent host default in `engine/flags.go:39-46` set `host = \"\"` for non-Windows, and `utils.JoinHostPort(\"\", \":5553\")` resolves to `\":5553\"` \u2014 a Go `http.Server.Addr` of `\":5553\"` listens on every interface. On Windows the same code chose `\"localhost\"`, binding loopback only.\n\nThe result was a platform split where the OS Algernon\u0027s dev workflow is most often used on (Linux/macOS) got the network-exposed default, and only Windows users got the loopback-safe one. A LAN peer with no developer interaction could connect to `\u003cdev-laptop-ip\u003e:5553` and read the file-change stream.\n\nThis advisory covers the bind-address default in isolation. The fix is independent of authentication (#2a) and CORS (#2b) \u2014 switching the default to loopback can be done without touching either.\n\n### Details\n\n#### Root cause \u2014 platform-dependent `host` default in `handleFlags`\n\n```go\n// engine/flags.go:39-46 (1.17.6)\nhost := \"\"\nif runtime.GOOS == \"windows\" {\n host = \"localhost\"\n // Default Bolt database file\n ac.defaultBoltFilename = filepath.Join(serverTempDir, \"algernon.db\")\n // Default log file\n ac.defaultLogFile = filepath.Join(serverTempDir, \"algernon.log\")\n}\n```\n\n```go\n// engine/config.go:388-391 (1.17.6, finalConfiguration)\nif ac.eventAddr == \"\" {\n ac.eventAddr = utils.JoinHostPort(host, ac.defaultEventColonPort)\n}\n```\n\nResult tabulated:\n\n| Platform | `host` | `eventAddr` after `JoinHostPort` | Effective bind |\n|---|---|---|---|\n| Linux | `\"\"` | `\":5553\"` | `0.0.0.0:5553` (all interfaces) |\n| macOS | `\"\"` | `\":5553\"` | `0.0.0.0:5553` (all interfaces) |\n| Windows | `\"localhost\"` | `\"localhost:5553\"` | `127.0.0.1:5553` (loopback) |\n\nThe same `host` value also governs the main web server bind, so the platform split affects both ports. The web-server bind on Linux/macOS is a separate (defensible) design decision \u2014 a server intended to be reachable; the SSE port is *not* such a service and inherited the same default by accident.\n\n#### Why this is an independent finding\n\nThe fix is a single line: change the default `host` value, or change the `eventAddr` default specifically, to `\"localhost\"` regardless of platform. No change to authentication or CORS is required to close the network-reach half of the original bundled advisory. A LAN peer can no longer connect \u2014 the listener is unreachable from another host \u2014 even if the SSE handler still has no authentication and still returns `Allow-Origin: *`.\n\n### PoC (against 1.17.6 on Linux/macOS)\n\n```bash\n# Operator\u0027s laptop on a hotel/cafe/office WiFi:\nalgernon -a /path/to/project\n# =\u003e SSE listener bound to 0.0.0.0:5553\n\n# Any peer on the same subnet:\n$ curl -sN http://\u003cdev-laptop-ip\u003e:5553/sse\nid: 0\ndata: /path/to/project/secret-notes.md\n\nid: 1\ndata: /path/to/project/.env.local\n```\n\nNo interaction from the developer is required. The peer needs network reach and nothing else.\n\n### Impact\n\n- **Confidentiality:** medium. LAN-bounded continuous information disclosure of filenames and edit timing.\n- **Integrity:** none.\n- **Availability:** none directly.\n\nThe CVSS vector uses `AV:A` (adjacent network) to model the LAN-only reach. The vector for a misconfigured deployment behind a NAT-less or routed network would shift to `AV:N` and rise to 5.3.\n\n### Suggestions to fix\n\n**Primary fix \u2014 pick `localhost` as the SSE default on every platform.**\n\n```go\n// engine/flags.go -- platform-independent default for the event listener\n// (keep the existing platform split for the WEB server if desired, but\n// not for the event server)\nhost := \"localhost\"\n```\n\nOr, more surgically:\n\n```go\n// engine/config.go -- finalConfiguration\nif ac.eventAddr == \"\" {\n ac.eventAddr = utils.JoinHostPort(\"localhost\", ac.defaultEventColonPort)\n}\n```\n\nAn operator who genuinely wants LAN-reachable SSE can pass `--eventserver 0.0.0.0:5553` explicitly and accept the consequences.\n\n**Stronger fix \u2014 eliminate the second listener entirely.** Mount the SSE handler on the main mux at `/sse`. The bind address is then whatever the main server uses; there is no second listener and therefore no second bind-address default to get wrong.\n\n### Live verification\n\nAudit-host bind check (Windows 10):\n\n```\n$ netstat -an | findstr 5553\n TCP 127.0.0.1:5553 0.0.0.0:0 LISTENING\n```\n\nConfirms the Windows default is `localhost`. The Linux/macOS bind to `0.0.0.0:5553` is documented in the code path above; it was not exercised on the audit machine because the audit host was Windows. A maintainer reproducing on a Linux host would see `0.0.0.0:5553 LISTENING` from `ss -tlnp`.",
"id": "GHSA-gj84-924c-48fx",
"modified": "2026-05-20T15:33:56Z",
"published": "2026-05-20T15:33:56Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/xyproto/algernon/security/advisories/GHSA-gj84-924c-48fx"
},
{
"type": "PACKAGE",
"url": "https://github.com/xyproto/algernon"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Algernon: Auto-refresh SSE event server binds to all interfaces by default on Linux/macOS"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.