GHSA-8whx-v8qq-pq64: changedetection.io has Reflected XSS in its RSS Tag Error Response
A reflected cross-site scripting (XSS) vulnerability was identified in the `/rss/tag/` endpoint of changedetection.io. The `tag_uuid` path parameter is reflected directly in the HTTP response body without HTML escaping. Since Flask returns `text/html` by default for plain string responses, the browser parses and executes injected JavaScript.This vulnerability persists in version **0.54.1**, which patched the related XSS in `/rss/watch/` (CVE-2026-27645 / GHSA-mw8m-398g-h89w) but did not address the identical pattern in the tag RSS endpoint.Package**Ecosystem:** pip**Package:** changedetection.io**Affected versions:** <= 0.54.1**Patched versions:** _(none yet)_Severity**Moderate - CVSS 6.1**
`CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N`Details**File:** `changedetectionio/blueprint/rss/tag.py` **Line:** 36 **Source:** [tag.py @ 1d72716](https://raw.githubusercontent.com/dgtlmoon/changedetection.io/1d72716c6988a4f6796bb85a5d42872800cd7a70/changedetectionio/blueprint/rss/tag.py)The `tag_uuid` parameter from the URL path is interpolated into the response body using an f-string with no escaping:Flask's default `Content-Type` for plain string responses is `text/html; charset=utf-8`, so any HTML/JavaScript injected via `{tag_uuid}` is rendered and executed by the browser.Relationship to CVE-2026-27645CVE-2026-27645 (GHSA-mw8m-398g-h89w) addressed the identical vulnerability pattern in `/rss/watch/` (`single_watch.py`). The fix applied in v0.54.1 patched that endpoint but **did not** fix the same pattern in `/rss/tag/` (`tag.py`). Testing confirms:**`/rss/watch/` on v0.54.1** — Returns generic 404 page, XSS no longer triggers ✅**`/rss/tag/` on v0.54.1** — XSS payload still fires, vulnerability confirmed ❌Attack VectorThe attack requires a valid RSS access token, which is a 32-character hex string exposed in the `<link>` HTML tag on the homepage without authentication:Attacker visits the target's homepage (if unauthenticated) and extracts the RSS token from the `<link>` tagCrafts a malicious URL:Sends the link to a victim who has an active session on the changedetection.io instanceWhen the victim clicks the link, the server responds with:The browser renders the `<img>` tag, the `onerror` fires, and JavaScript executes in the victim's session contextProof of ConceptRequestResponseThe XSS payload is reflected unescaped in an HTML response. The browser executes `alert(document.domain)` and displays "localhost", confirming JavaScript execution.**Tested on:** changedetection.io v0.54.1 (Docker, localhost, Feb 25, 2026)https://github.com/user-attachments/assets/6db07f6a-6df8-48a7-a597-9f39dfa1bb29Impact**Session cookie theft** via `document.cookie` exfiltration**Account takeover** if session cookies lack the `HttpOnly` flag**Phishing** via crafted links that appear to originate from a trusted changedetection.io instance**Low exploitation barrier** - the RSS token is obtainable without authentication from the homepage `<link>` tag**Widespread exposure** - prior scanning of internet-facing instances (during CVE-2026-27645 research) identified 500+ publicly accessible deploymentsSuggested FixEscape the `tag_uuid` parameter before reflecting it in the response, or set the `Content-Type` to `text/plain`:Option A: HTML Escape (Recommended)Option B: Set Content-Type to text/plainCredits**Roberto Nunes** ([@Akokonunes](https://github.com/Akokonunes)) - Reporter**neo-ai-engineer** ([@neo-ai-engineer](https://github.com/neo-ai-engineer)) - ReporterReferencesRelated advisory: [GHSA-mw8m-398g-h89w](https://github.com/dgtlmoon/changedetection.io/security/advisories/GHSA-mw8m-398g-h89w) (CVE-2026-27645)Vulnerable source: [tag.py @ 1d72716](https://raw.githubusercontent.com/dgtlmoon/changedetection.io/1d72716c6988a4f6796bb85a5d42872800cd7a70/changedetectionio/blueprint/rss/tag.py)
CVSS v3.1: 6.1