CVE-2026-29038Medium· 6.1▾ Sunlitchangedetection.io has Reflected XSS in its RSS Tag Error Response
▾ Sunlit zone — Low / medium · no exploitation signal
impact 33.6 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 13.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via OSV
Last analysed / modified upstream
0.3%
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.
Moderate - CVSS 6.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
File: changedetectionio/blueprint/rss/tag.py Line: 36 Source: tag.py @ 1d72716
The tag_uuid parameter from the URL path is interpolated into the response body using an f-string with no escaping:
tag = datastore.data['settings']['application'].get('tags', {}).get(tag_uuid)
if not tag:
return f"Tag with UUID {tag_uuid} not found", 404 # ← No escaping, Content-Type: text/html
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.
CVE-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 ❌The 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> tag
Crafts a malicious URL:
http://target:5000/rss/tag/<img src=x onerror=alert(document.cookie)>?token=EXTRACTED_TOKEN
Sends the link to a victim who has an active session on the changedetection.io instance
When the victim clicks the link, the server responds with:
Tag with UUID <img src=x onerror=alert(document.cookie)> not found
The browser renders the <img> tag, the onerror fires, and JavaScript executes in the victim's session context
GET /rss/tag/%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E?token=60b83b06df98b24c66367bc3d233105b HTTP/1.1
Host: localhost:5000
HTTP/1.1 404 NOT FOUND
Content-Type: text/html; charset=utf-8
Tag with UUID <img src=x onerror=alert(document.domain)> not found
The 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-9f39dfa1bb29
document.cookie exfiltrationHttpOnly flag<link> tagEscape the tag_uuid parameter before reflecting it in the response, or set the Content-Type to text/plain:
from markupsafe import escape
if not tag:
return f"Tag with UUID {escape(tag_uuid)} not found", 404
from flask import make_response
if not tag:
resp = make_response(f"Tag with UUID {tag_uuid} not found", 404)
resp.headers['Content-Type'] = 'text/plain; charset=utf-8'
return resp
changedetection-io < 0.54.4Upgrade to a patched release:
changedetection-io 0.54.4Connected by shared product, vendor, weakness, or advisory.
CVE-2026-27645Medium· 6.1changedetection.io Vulnerable to Reflected XSS in RSS Single Watch Error Response
CVE-2026-29039Highchangedetection.io vulnerable to XPath - Arbitrary File Read via unparsed-text()
CVE-2026-29065Highchangedetection.io has Zip Slip vulnerability in the backup restore functionality
CVE-2024-23329Low· 3.7changedetection.io API endpoint is not secured with API token
CVE-2024-32651Critical· 10.0changedetection.io has a Server Side Template Injection using Jinja2 which allows Remote Command Execution
CVE-2026-33981HighChangedetection.io Discloses Environment Variables via jq env Builtin in Include Filters