{"record":{"id":"119cb487397a72d9","repo":"DIYgod/RSSHub","slug":"this-rss-is-disabled-unless-allow-user-supply-uns-119cb4","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":"error","filePath":"lib/routes/javdb/utils.ts","lineNumber":17,"sourceCode":"import { load } from 'cheerio';\n\nimport { config } from '@/config';\nimport ConfigNotFoundError from '@/errors/types/config-not-found';\nimport type { DataItem } from '@/types';\nimport cache from '@/utils/cache';\nimport logger from '@/utils/logger';\nimport { parseDate } from '@/utils/parse-date';\nimport { getPlaywrightPage } from '@/utils/playwright';\n\nconst allowDomain = new Set(['javdb.com', 'javdb571.com', 'javdb36.com', 'javdb007.com', 'javdb521.com']);\n\nconst ProcessItems = async (ctx, currentUrl, title) => {\n    const domain = ctx.req.query('domain') ?? 'javdb.com';\n    const url = new URL(currentUrl, `https://${domain}`);\n    if (!config.feature.allow_user_supply_unsafe_domain && !allowDomain.has(url.hostname)) {\n        throw new ConfigNotFoundError(`This RSS is disabled unless 'ALLOW_USER_SUPPLY_UNSAFE_DOMAIN' is set to 'true'.`);\n    }\n\n    const rootUrl = `https://${domain}`;\n\n    const { page, destroy, context } = await getPlaywrightPage(url.href, {\n        onBeforeLoad: async (page) => {\n            if (config.javdb.session) {\n                await page.context().addCookies([\n                    {\n                        name: '_jdb_session',\n                        value: config.javdb.session,\n                        domain,\n                        path: '/',\n                    },\n                ]);\n            }\n            await page.route('**/*', (route) => {\n                const request = route.request();","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/javdb/utils.ts#L1-L35","documentation":"Thrown by the javdb route's ProcessItems as a ConfigNotFoundError when the user-supplied `domain` query parameter is neither in the hardcoded allowDomain set ({javdb.com, javdb571.com, javdb36.com, javdb007.com, javdb521.com}) nor explicitly permitted via the ALLOW_USER_SUPPLY_UNSAFE_DOMAIN feature flag. It is an SSRF-guard: the route builds a Playwright URL from arbitrary user input, so only trusted domains are allowed by default.","triggerScenarios":"A request like /javdb?domain=evil.example.com (or any domain not in the allow-set) while config.feature.allow_user_supply_unsafe_domain is false/undefined. The new URL constructor accepts it, allowDomain.has() returns false, and the guard fires.","commonSituations":"Operator points the route at a javdb mirror not yet in the allow-list; self-hosters who have not set ALLOW_USER_SUPPLY_UNSAFE_DOMAIN=true; typos in the domain query param; attempts to abuse the route as an open proxy.","solutions":["Use one of the whitelisted domains, e.g. /javdb?domain=javdb571.com","If you run your own instance and trust the new mirror, add its hostname to allowDomain in lib/routes/javdb/utils.ts:9","Alternatively set ALLOW_USER_SUPPLY_UNSAFE_DOMAIN=true in the instance config (only when you understand the SSRF implications)","Verify the hostname exactly — allowDomain.has uses exact match, so www.javdb.com is rejected even though javdb.com is allowed"],"exampleFix":"// before\nconst allowDomain = new Set(['javdb.com', 'javdb571.com', 'javdb36.com']);\n// after — add the mirror you actually use\nconst allowDomain = new Set(['javdb.com', 'javdb571.com', 'javdb36.com', 'javdb007.com', 'javdb521.com', 'javdb618.com']);","handlingStrategy":"validation","validationCode":"import config from '@/utils/config';\nconst ALLOW_DOMAIN = new Set(['javdb.com', 'javdb571.com', 'javdb36.com', 'javdb007.com', 'javdb521.com']);\nfunction assertAllowedDomain(domain: string | undefined): void {\n  const d = domain ?? 'javdb.com';\n  const host = new URL(currentUrl, `https://${d}`).hostname;\n  if (!config.feature.allow_user_supply_unsafe_domain && !ALLOW_DOMAIN.has(host)) {\n    throw new ConfigNotFoundError(`domain '${host}' not allowed`);\n  }\n}","typeGuard":"const ALLOW_DOMAIN = new Set(['javdb.com', 'javdb571.com', 'javdb36.com', 'javdb007.com', 'javdb521.com']);\nfunction isAllowedJavdbDomain(domain: string): boolean {\n  return ALLOW_DOMAIN.has(domain);\n}","tryCatchPattern":"// Validate before invoking ProcessItems\nif (!isAllowedJavdbDomain(domain) && !config.feature.allow_user_supply_unsafe_domain) {\n  return ctx.json({ error: 'domain not allowed' }, 400);\n}\ntry { return await ProcessItems(ctx, currentUrl, title); }\ncatch (e) {\n  if (e instanceof ConfigNotFoundError) { /* user-facing 400 */ }\n  throw e;\n}","preventionTips":["Whitelist exact hostnames only; document that subdomains are not auto-allowed","Gate the feature flag behind an explicit operator decision (SSRF risk)","Keep the allow-list in a shared constant so route + tests reference one source","Reject before any network allocation (Playwright) on invalid domains"],"tags":["security","ssrf","config","validation"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}