RSSHub Cross-site Scripting vulnerability caused by internal media proxy
Description
RSSHub internal media proxy in versions before 4d3e5d7 fails to sanitize images, leading to stored XSS that executes arbitrary JavaScript when a crafted image URL is accessed.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
RSSHub internal media proxy in versions before 4d3e5d7 fails to sanitize images, leading to stored XSS that executes arbitrary JavaScript when a crafted image URL is accessed.
Vulnerability
Overview
RSSHub, an open-source RSS feed generator, contains a cross-site scripting (XSS) vulnerability in its internal media proxy component. In versions starting from 1.0.0-master.cbbd829 up to but not including 4d3e5d7, the proxy does not properly handle specially crafted image files. When such an image is supplied to the proxy, it proxies the image without sanitization, allowing malicious JavaScript code to be injected into the response [1][3].
Attack
Vector
An attacker can craft a malicious image containing embedded JavaScript and provide it to the RSSHub media proxy endpoint. Users who then access the deliberately constructed URL (e.g., via an RSS feed that includes the image) will have the malicious script executed in their browser context. No authentication or special network position is required; the attacker only needs to host the crafted image or cause the proxy to process it [1][3].
Impact
Successful exploitation allows arbitrary JavaScript execution in the context of the victim's browser. This can lead to data theft, session hijacking, or other client-side attacks. The vulnerability is particularly dangerous because RSSHub instances often serve content to multiple users, and a single malicious feed can affect all subscribers who view the image via the proxy [1][3].
Mitigation
The issue is patched in version 4d3e5d7 of RSSHub, which removes the internal media proxy functionality (commit `4d3e5d7` [2]). Users should upgrade to this version or later. No known workarounds exist; the only remedy is to apply the fix [1][3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rsshubnpm | >= 1.0.0-master.cbbd829, < 1.0.0-master.d8ca915 | 1.0.0-master.d8ca915 |
Affected products
2Patches
14d3e5d79c1c1feat: remove MEDIA_PROXY_KEY and internal media proxy
12 files changed · +5 −73
lib/config.ts+0 −2 modified@@ -61,7 +61,6 @@ export type Config = { allow_user_hotlink_template: boolean; filter_regex_engine: string; allow_user_supply_unsafe_domain: boolean; - mediaProxyKey?: string; }; suffix?: string; titleLengthLimit: number; @@ -391,7 +390,6 @@ const calculateValue = () => { allow_user_hotlink_template: toBoolean(envs.ALLOW_USER_HOTLINK_TEMPLATE, false), filter_regex_engine: envs.FILTER_REGEX_ENGINE || 're2', allow_user_supply_unsafe_domain: toBoolean(envs.ALLOW_USER_SUPPLY_UNSAFE_DOMAIN, false), - mediaProxyKey: envs.MEDIA_PROXY_KEY, }, suffix: envs.SUFFIX, titleLengthLimit: toInt(envs.TITLE_LENGTH_LIMIT, 150),
lib/routes/rsshub/maintainer.ts+0 −1 modified@@ -1,5 +1,4 @@ export default { - '/m/:key/:url': ['TonyRL'], '/routes/:lang?': ['DIYgod'], '/transform/html/:url/:routeParams': ['ttttmr'], '/transform/json/:url/:routeParams': ['ttttmr'],
lib/routes/rsshub/media.ts+0 −52 removed@@ -1,52 +0,0 @@ -// @ts-nocheck -import got from '@/utils/got'; -import { config } from '@/config'; -const { getDomain } = require('tldts'); -const { refererMap } = require('./referer-map'); - -export default async (ctx) => { - if (!config.feature.mediaProxyKey) { - throw new Error('Internal media proxy is disabled.'); - } - - const key = ctx.req.param('key'); - if (key !== config.feature.mediaProxyKey) { - throw new Error('Invalid media proxy key.'); - } - - const url = decodeURIComponent(ctx.req.param('url')); - const requestUrl = new URL(url); - const { hostname, origin } = requestUrl; - - const domain = getDomain(hostname); - - let referer = refererMap.get(domain); - referer ||= origin; - - const { headers } = await got.head(url, { - headers: { - referer, - }, - }); - - const cacheControl = headers['cache-control']; - const contentType = headers['content-type']; - const contentLength = headers['content-length']; - - if (!contentType.startsWith('image/') || headers.server === 'RSSHub') { - return ctx.redirect(url); - } - - ctx.set({ - 'cache-control': cacheControl || `public, max-age=${config.cache.contentExpire}`, - 'content-length': contentLength, - 'content-type': contentType, - server: 'RSSHub', - }); - - ctx.body = await got.stream(url, { - headers: { - referer, - }, - }); -};
lib/routes/rsshub/router.ts+0 −1 modified@@ -1,5 +1,4 @@ export default (router) => { - router.get('/m/:key/:url', './media'); router.get('/routes/:lang?', './routes'); router.get('/transform/html/:url/:routeParams', './transform/html'); router.get('/transform/json/:url/:routeParams', './transform/json');
lib/routes/telegram/channel.ts+1 −1 modified@@ -55,7 +55,7 @@ const mediaTagDict = { }; export default async (ctx) => { - const useWeb = ctx.req.param('routeParams') || !(config.telegram.session && config.feature.mediaProxyKey); + const useWeb = ctx.req.param('routeParams') || !config.telegram.session; if (!useWeb) { return require('./tglib/channel').default(ctx); }
lib/routes/telegram/tglib/channel.ts+0 −4 modified@@ -33,10 +33,6 @@ function parseRange(range, length) { } async function getMedia(ctx) { - if (ctx.req.param('key') !== config.feature.mediaProxyKey) { - throw new Error('Invalid key'); - } - const media = await decodeMedia(ctx.req.param('username'), ctx.req.param('media')); if (!media) { ctx.status = 500;
lib/routes/telegram/tglib/client.ts+1 −1 modified@@ -69,7 +69,7 @@ function ExpandInlineBytes(bytes) { } function getMediaLink(ctx, channel, channelName, message) { - const base = `${ctx.protocol}://${ctx.host}/telegram/channel/${channelName}/${config.feature.mediaProxyKey}/`; + const base = `${ctx.protocol}://${ctx.host}/telegram/channel/${channelName}`; const src = base + `${channel.channelId}_${message.id}`; const x = message.media;
lib/routes/weibo/timeline.ts+1 −1 modified@@ -145,7 +145,7 @@ export default async (ctx) => { 'Content-Type': 'text/html; charset=UTF-8', 'Cache-Control': 'no-cache', }); - ctx.body = `<script>window.location = '/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}'</script>`; + ctx.html(`<script>window.location = '/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}'</script>`); } } else { const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo;
website/docs/install/config.md+0 −2 modified@@ -198,8 +198,6 @@ Configs in this sections are in beta stage, and **are turn off by default**. Ple `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: allow users to provide a domain as a parameter to routes that are not in their allow list, respectively. Public instances are suggested to leave this value default, as it may lead to [Server-Side Request Forgery (SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) -`MEDIA_PROXY_KEY`: the access key for internal media proxy. - ## Other Application Configurations `DISALLOW_ROBOT`: prevent indexing by search engine, default to enable, set false or 0 to disable
website/docs/routes/other.mdx+1 −5 modified@@ -121,7 +121,7 @@ See [#app-store-mac-app-store](/routes/program-update#app-store-mac-app-store) <Route author="DIYgod" example="/scmp/coronavirus" path="/scmp/coronavirus" /> -### Macao Pagina Electrónica Especial Contra Epidemias: What’s New {#corona-virus-disease-2019-macao-pagina-electr%C3%B3nica-especial-contra-epidemias-what-s-new} +### Macao Pagina Electrónica Especial Contra Epidemias: What’s New {#corona-virus-disease-2019-macao-pagina-electronica-especial-contra-epidemias-what-s-new} Official Website: [https://www.ssm.gov.mo/apps1/PreventWuhanInfection/en.aspx](https://www.ssm.gov.mo/apps1/PreventWuhanInfection/en.aspx) @@ -442,10 +442,6 @@ It is recommended to use with clipping tools such as Notion Web Clipper. ## RSSHub {#rsshub} -### Internal Media Proxy {#rsshub-internal-media-proxy} - -<Route author="TonyRL" example="/rsshub/m/key/https%3A%2F%2Fdocs.rsshub.app%2Fimg%2Flogo.png" path="/rsshub/m/:key/:url" paramsDesc={['Media Proxy Key', '`encodeURIComponent`ed URL address']} configRequired="1" /> - ### Transformation - HTML {#rsshub-transformation-html} Pass URL and transformation rules to convert HTML/JSON into RSS.
website/docs/routes/social-media.mdx+1 −1 modified@@ -784,7 +784,7 @@ If the instance address is not `mastodon.social` or `pawoo.net`, then the route <Route author="synchrone" example="/telegram/channel/telegram" path="/telegram/channel/:username" paramsDesc={['Channel name, without @']} configRequired="1" /> :::warning -This route requires user-based `TELEGRAM_SESSION` which can be acquired and `MEDIA_PROXY_KEY`. +This route requires user-based `TELEGRAM_SESSION`. ::: ### Sticker Pack {#telegram-sticker-pack}
website/i18n/zh/docusaurus-plugin-content-docs/current/install/config.md+0 −2 modified@@ -196,8 +196,6 @@ RSSHub 支持使用访问密钥 / 码进行访问控制。开启将会激活全 `ALLOW_USER_SUPPLY_UNSAFE_DOMAIN`: 允许用户为路由提供域名作为参数。建议公共实例不要调整此选项,开启后可能会导致 [服务端请求伪造(SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) -`MEDIA_PROXY_KEY`: 内置多媒体代理的访问密钥 - ## 其他应用配置 `DISALLOW_ROBOT`: 阻止搜索引擎收录,默认开启,设置 false 或 0 关闭
Vulnerability mechanics
Root cause
"The internal media proxy returns user-supplied content with the original Content-Type without sanitizing for executable script content, enabling cross-site scripting."
Attack vector
An attacker crafts a URL that points to a specially crafted image containing embedded JavaScript. The attacker then supplies this URL to the internal media proxy endpoint (`/rsshub/m/:key/:url`). When a victim accesses the resulting proxied URL, the proxy returns the resource with the original `Content-Type` (e.g., `image/svg+xml`), but the content includes executable script code. Because the proxy does not neutralize the response [CWE-79], the script executes in the victim's browser context, leading to cross-site scripting.
Affected code
The vulnerable code resides in `lib/routes/rsshub/media.ts`, which implemented an internal media proxy. This route accepted a user-supplied URL via the `:url` parameter, fetched the resource, and returned it with the original `Content-Type` header without sanitizing the response for executable content. The entire file was removed in the patch [patch_id=1640317].
What the fix does
The patch removes the entire internal media proxy feature by deleting `lib/routes/rsshub/media.ts` and its associated configuration (`mediaProxyKey` from `lib/config.ts` and the `MEDIA_PROXY_KEY` environment variable documentation). It also updates the Telegram channel route to no longer rely on the media proxy key. By eliminating the proxy entirely, the fix prevents any possibility of serving un-sanitized user-supplied content with attacker-controlled MIME types, closing the XSS vector [patch_id=1640317].
Preconditions
- configThe RSSHub instance must have the internal media proxy enabled (MEDIA_PROXY_KEY configured).
- inputThe attacker must know or guess the media proxy key value.
- networkThe victim must access the crafted proxied URL (e.g., via a link or embedded resource).
Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-2wqw-hr4f-xrhhghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-27926ghsaADVISORY
- github.com/DIYgod/RSSHub/commit/4d3e5d79c1c17837e931b4cd253d2013b487aa87ghsax_refsource_MISCWEB
- github.com/DIYgod/RSSHub/security/advisories/GHSA-2wqw-hr4f-xrhhghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.