VYPR
\n```\n\nThe exploit is cookie-less and CORS-clean — no SameSite, no third-party-cookie restriction, no preflight challenge applies. The user interaction is \"visit a webpage,\" which `UI:R` in the CVSS vector reflects.\n\n### PoC (against 1.17.6)\n\n```bash\n# 1. Operator: algernon -a /path/to/project on Windows; SSE at localhost:5553\n# 2. Attacker lures the developer to https://news.example:\n# The page contains the snippet above.\n# 3. EventSource opens, browser sends the request; algernon responds with\n# Access-Control-Allow-Origin: *, browser passes message events to the\n# cross-origin script; script ships filenames to attacker.example.\n```\n\nCLI reproduction of the header is identical to advisory #2a's transcript; the relevant evidence is the `Access-Control-Allow-Origin: *` value in the response, not the body.\n\n### Impact\n\n- **Confidentiality:** medium. Cross-origin browser-tab read access to the file-change stream, with no server-side knowledge that the read happened.\n- **Integrity:** none.\n- **Availability:** none directly (the cross-origin tab does not exhaust resources beyond the user's own browser).\n\n### Suggestions to fix\n\n**Primary fix — echo a same-origin allow-list instead of `*`.**\n\n```go\n// vendor/github.com/xyproto/recwatch/eventserver.go -- in GenFileChangeEvents\norigin := r.Header.Get(\"Origin\")\nif !isAllowedOrigin(origin) {\n http.Error(w, \"forbidden\", http.StatusForbidden)\n return\n}\nw.Header().Set(\"Access-Control-Allow-Origin\", origin)\nw.Header().Set(\"Vary\", \"Origin\")\n```\n\nThe `allowed` parameter must change from `\"*\"` to an explicit allow-list (or a single canonical server origin) — for example, `sseScheme + \"://\" + ac.serverAddr`. With the server's own scheme+host+port in `Allow-Origin`, a cross-origin request from `evil.example` is rejected by the browser because the response advertises a different origin.\n\n**Defence in depth — drop the legacy dedicated-port code path.** Mounting the SSE handler on the main mux instead lets the response omit `Access-Control-Allow-Origin` entirely (same-origin only by default). The dedicated `--eventserver`-style path is the only place `Access-Control-Allow-Origin` is set in the codebase; removing the dedicated path simplifies the surface.\n\n### Live verification\n\n```\n$ ./algernon.exe --nodb --httponly --server -a --addr 127.0.0.1:18779 --quiet poc2/site\n$ ( curl -sNi --max-time 2 -H \"Origin: http://evil.example\" http://127.0.0.1:5553/sse > sse.txt &\n sleep 1\n echo \"trigger\" >> poc2/site/probe.txt\n wait )\n$ cat sse.txt\nHTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\nCache-Control: no-cache\nConnection: keep-alive\nContent-Type: text/event-stream;charset=utf-8\n...\nid: 0\ndata: C:\\Users\\xbox\\Desktop\\VulnTesting\\algernon-main\\poc-test\\poc2\\site\\probe.txt\n```\n\nThe `Origin: http://evil.example` request header was echoed back as `Access-Control-Allow-Origin: *` (the wildcard — browsers treat this as \"any origin may read\"). A cross-origin tab at any URL can run `new EventSource(\"http://:5553/sse\")` and read the stream.","additionalType":"https://schema.org/SoftwareApplication","sameAs":["https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-46431"]},"keywords":"CVE-2026-46431, medium, Xyproto Algernon, Xyproto Algernon","mentions":[{"@type":"SoftwareApplication","name":"Algernon","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Xyproto"}},{"@type":"SoftwareApplication","name":"Algernon","applicationCategory":"SecurityApplication","publisher":{"@type":"Organization","name":"Xyproto"}}],"isAccessibleForFree":true},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://portal.vyprsec.ai/"},{"@type":"ListItem","position":2,"name":"CVEs","item":"https://portal.vyprsec.ai/cves"},{"@type":"ListItem","position":3,"name":"CVE-2026-46431","item":"https://portal.vyprsec.ai/cves/CVE-2026-46431"}]}]}
Medium severity4.3GHSA Advisory· Published May 20, 2026· Updated May 20, 2026

Algernon: Auto-refresh SSE event server sets Access-Control-Allow-Origin: *

CVE-2026-46431

Description

Summary

The SSE event server's Access-Control-Allow-Origin response header was hardcoded to the wildcard * regardless of the caller's Origin. Because EventSource does not preflight and does not send cookies, the wildcard is sufficient to let any third-party page the developer visits open a cross-origin EventSource to the SSE port and read the live filename stream from JavaScript. Combined with the lack of authentication (advisory #2a), no further trickery is required — any tab the developer opens has script-level read access to the stream.

This advisory covers the CORS configuration in isolation. The fix is independent of authentication and bind-address fixes: the wildcard could be replaced with a same-origin echo without touching either.

Details

Root cause — hard-coded "*" passed as the CORS allowed-origin
// engine/config.go  (1.17.6, MustServe)
recwatch.EventServer(absdir, "*", ac.eventAddr, ac.defaultEventPath, ac.refreshDuration)

The literal "*" is the second positional argument. The vendored recwatch implementation reflects it verbatim into the response header:

// vendor/github.com/xyproto/recwatch/eventserver.go:100-108  (1.17.6)
func GenFileChangeEvents(events TimeEventMap, mut *sync.Mutex, maxAge time.Duration, allowed string) http.HandlerFunc {
    return func(w http.ResponseWriter, _ *http.Request) {
        w.Header().Set("Content-Type", "text/event-stream;charset=utf-8")
        w.Header().Set("Cache-Control", "no-cache")
        w.Header().Set("Connection", "keep-alive")
        w.Header().Set("Access-Control-Allow-Origin", allowed)
        ...
    }
}

There is no decision based on the request's Origin header, and no allow-list mechanism — every caller is told their origin is approved.

Why the wildcard is exploitable

EventSource opens a GET request, never sends a preflight, and never carries cookies. The same-origin policy normally still blocks the response body from being read by JavaScript at a different origin — that is the role of Access-Control-Allow-Origin. When the server returns *, the browser permits the cross-origin script to read every message event.

So a developer running algernon -a on their workstation, with the SSE listener at http://127.0.0.1:5553/sse (Windows) or http://0.0.0.0:5553/sse (Linux/macOS), only needs to visit *any* third-party origin in another tab for the following to drain their stream silently:

<!doctype html>

The exploit is cookie-less and CORS-clean — no SameSite, no third-party-cookie restriction, no preflight challenge applies. The user interaction is "visit a webpage," which UI:R in the CVSS vector reflects.

PoC (against 1.17.6)

# 1. Operator: algernon -a /path/to/project  on Windows; SSE at localhost:5553
# 2. Attacker lures the developer to https://news.example:
#    The page contains the snippet above.
# 3. EventSource opens, browser sends the request; algernon responds with
#    Access-Control-Allow-Origin: *, browser passes message events to the
#    cross-origin script; script ships filenames to attacker.example.

CLI reproduction of the header is identical to advisory #2a's transcript; the relevant evidence is the Access-Control-Allow-Origin: * value in the response, not the body.

Impact

  • Confidentiality: medium. Cross-origin browser-tab read access to the file-change stream, with no server-side knowledge that the read happened.
  • Integrity: none.
  • Availability: none directly (the cross-origin tab does not exhaust resources beyond the user's own browser).

Suggestions to fix

**Primary fix — echo a same-origin allow-list instead of *.**

// vendor/github.com/xyproto/recwatch/eventserver.go -- in GenFileChangeEvents
origin := r.Header.Get("Origin")
if !isAllowedOrigin(origin) {
    http.Error(w, "forbidden", http.StatusForbidden)
    return
}
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Vary", "Origin")

The allowed parameter must change from "*" to an explicit allow-list (or a single canonical server origin) — for example, sseScheme + "://" + ac.serverAddr. With the server's own scheme+host+port in Allow-Origin, a cross-origin request from evil.example is rejected by the browser because the response advertises a different origin.

Defence in depth — drop the legacy dedicated-port code path. Mounting the SSE handler on the main mux instead lets the response omit Access-Control-Allow-Origin entirely (same-origin only by default). The dedicated --eventserver-style path is the only place Access-Control-Allow-Origin is set in the codebase; removing the dedicated path simplifies the surface.

Live verification

$ ./algernon.exe --nodb --httponly --server -a --addr 127.0.0.1:18779 --quiet poc2/site
$ ( curl -sNi --max-time 2 -H "Origin: http://evil.example" http://127.0.0.1:5553/sse > sse.txt &
    sleep 1
    echo "trigger" >> poc2/site/probe.txt
    wait )
$ cat sse.txt
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/event-stream;charset=utf-8
...
id: 0
data: C:\Users\xbox\Desktop\VulnTesting\algernon-main\poc-test\poc2\site\probe.txt

The Origin: http://evil.example request header was echoed back as Access-Control-Allow-Origin: * (the wildcard — browsers treat this as "any origin may read"). A cross-origin tab at any URL can run new EventSource("http://:5553/sse") and read the stream.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Algernon's SSE event server hardcodes `Access-Control-Allow-Origin: *`, letting any cross-origin page read the live file-change stream.

Vulnerability

The Server-Sent Events (SSE) endpoint in Algernon versions up to and including 1.17.6 hardcodes the Access-Control-Allow-Origin response header to the wildcard * [1], [2]. The affected code paths are in engine/config.go where recwatch.EventServer(absdir, "*", ...) is called and in the vendored vendor/github.com/xyproto/recwatch/eventserver.go where the second argument (the allowed string) is written verbatim as the response header [2], [3]. No per-request origin validation or allow-list is applied; every EventSource connection receives the wildcard value.

Exploitation

An attacker can craft a malicious web page that, when visited by a developer who is running Algernon with the default SSE server enabled, opens a cross-origin EventSource to the SSE port. Because EventSource uses simple GET requests (no preflight) and does not send cookies, the wildcard Access-Control-Allow-Origin response is sufficient to allow the browser to expose the full response body to the calling JavaScript [2], [3]. No authentication, user interaction beyond visiting the page, or additional network position is required.

Impact

A successful exploit allows any third-party web page to read the live stream of filenames generated by Algernon's file-watching feature. This leaks information about which files are being edited or saved on the developer's system, potentially disclosing project structure, secrets, or other sensitive file names [1], [2], [3]. The stream does not include file contents, but the filename disclosure can aid further attacks.

Mitigation

The fix involves replacing the wildcard with a same-origin echo that reflects the request's Origin header, or restricting the endpoint to same-origin requests only. No official patched version of Algernon has been released as of the publication date [1], [2], [3]; users are advised to monitor the upstream repository for a security update. In the interim, disabling the SSE event server or binding it to localhost-only (where possible) reduces exposure.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Xyproto/AlgernonGHSA2 versions
    <= 1.17.6+ 1 more
    • (no CPE)range: <= 1.17.6
    • (no CPE)range: 1.17.6

Patches

0

No patches discovered yet.

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

2

News mentions

0

No linked articles in our index yet.