{"record":{"id":"58f2f72c86dca7c3","repo":"DIYgod/RSSHub","slug":"this-rss-is-disabled-unless-allow-user-supply-uns-58f2f7","errorCode":null,"errorMessage":"This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.","messagePattern":"This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'\\.","errorType":"exception","errorClass":"ConfigNotFoundError","httpStatus":null,"severity":"warning","filePath":"lib/routes/mastodon/tag.ts","lineNumber":37,"sourceCode":"        only_media: {\n            description: 'whether only display media content, default to false, any value to true',\n            options: [\n                { value: 'true', label: 'true' },\n                { value: 'false', label: 'false' },\n            ],\n            default: 'false',\n        },\n    },\n    name: 'Hashtag timeline',\n    maintainers: ['yuikisaito'],\n    handler,\n};\n\nasync function handler(ctx: Context): Promise<Data> {\n    const { site, hashtag } = ctx.req.param();\n    const only_media = ctx.req.param('only_media') === 'true' ? 'true' : 'false';\n    if (!config.feature.allow_user_supply_unsafe_domain && !utils.allowSiteList.includes(site)) {\n        throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);\n    }\n\n    const url = `http://${site}/api/v1/timelines/tag/${hashtag}?only_media=${only_media}`;\n\n    const response = await got.get(url, { headers: utils.apiHeaders(site) });\n    const list = response.data;\n\n    return {\n        title: `#${hashtag} ${only_media === 'true' ? ' Media' : ''} Timeline on ${site}`,\n        link: `https://${site}`,\n        item: utils.parseStatuses(list),\n    };\n}\n","sourceCodeStart":19,"sourceCodeEnd":51,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/mastodon/tag.ts#L19-L51","documentation":"ConfigNotFoundError thrown by the Mastodon hashtag timeline route when :site is not allowlisted and ALLOW_USER_SUPPLY_UNSAFE_DOMAIN is false. Same SSRF guard pattern as the other Mastodon site-param routes; the hashtag is then fetched from http://<site>/api/v1/timelines/tag/<hashtag>.","triggerScenarios":"GET /mastodon/tag/:site/:hashtag where :site is not mastodon.social/pawoo.net/fosstodon.org/MASTODON_API_HOST and the unsafe-domain feature flag is off.","commonSituations":"Following a hashtag on a non-allowlisted instance; public RSSHub deployment with the guard intentionally on; typo in the site segment of the route.","solutions":["Set ALLOW_USER_SUPPLY_UNSAFE_DOMAIN=true for a private/trusted deployment.","Set MASTODON_API_HOST to your instance to add it to the allowlist.","Verify the site segment has no protocol or path (just the host, e.g. 'fosstodon.org').","On public instances, keep the guard on and request an allowlist addition instead."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const ALLOWED = ['mastodon.social','pawoo.net','fosstodon.org', config.mastodon.apiHost].filter(Boolean);\nif (!ALLOWED.includes(site) && !config.feature.allow_user_supply_unsafe_domain) {\n    throw new ConfigNotFoundError('Instance not allowlisted; set ALLOW_USER_SUPPLY_UNSAFE_DOMAIN=true or MASTODON_API_HOST');\n}","typeGuard":"const isAllowlistedSite = (site: string): boolean =>\n    ['mastodon.social','pawoo.net','fosstodon.org', config.mastodon.apiHost].filter(Boolean).includes(site);","tryCatchPattern":null,"preventionTips":["Validate the site segment at the route boundary.","Use MASTODON_API_HOST for instances you subscribe to often.","Keep the SSRF guard enabled on public deployments."],"tags":["mastodon","config","ssrf","config-not-found","security"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}