{"record":{"id":"82f566de92af812a","repo":"MagicMirrorOrg/MagicMirror","slug":"forbidden-private-or-reserved-addresses-are-not-a","errorCode":null,"errorMessage":"Forbidden: private or reserved addresses are not allowed","messagePattern":"Forbidden: private or reserved addresses are not allowed","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"js/server_functions.js","lineNumber":85,"sourceCode":"\t\tif (!match) {\n\t\t\turl = `invalid url: ${req.url}`;\n\t\t\tLog.error(url);\n\t\t\treturn res.status(400).send(url);\n\t\t} else {\n\t\t\turl = match[1];\n\t\t\tif (typeof global.config !== \"undefined\") {\n\t\t\t\tif (config.hideConfigSecrets) {\n\t\t\t\t\turl = replaceSecretPlaceholder(url);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validate protocol before attempting connection (non-http/https are never allowed)\n\t\t\tlet parsed;\n\t\t\ttry {\n\t\t\t\tparsed = new URL(url);\n\t\t\t} catch {\n\t\t\t\tLog.warn(`SSRF blocked (invalid URL): ${url}`);\n\t\t\t\treturn res.status(403).json({ error: \"Forbidden: private or reserved addresses are not allowed\" });\n\t\t\t}\n\t\t\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\t\t\tLog.warn(`SSRF blocked (protocol): ${url}`);\n\t\t\t\treturn res.status(403).json({ error: \"Forbidden: private or reserved addresses are not allowed\" });\n\t\t\t}\n\n\t\t\t// Block localhost by hostname before even creating the dispatcher (no DNS needed).\n\t\t\tif (parsed.hostname.toLowerCase() === \"localhost\") {\n\t\t\t\tLog.warn(`SSRF blocked (localhost): ${url}`);\n\t\t\t\treturn res.status(403).json({ error: \"Forbidden: private or reserved addresses are not allowed\" });\n\t\t\t}\n\n\t\t\t// Whitelist check: if enabled, only allow explicitly listed domains\n\t\t\tif (global.config.cors === \"allowWhitelist\" && !global.config.corsDomainWhitelist.includes(parsed.hostname.toLowerCase())) {\n\t\t\t\tLog.warn(`CORS blocked (not in whitelist): ${url}`);\n\t\t\t\treturn res.status(403).json({ error: \"Forbidden: domain not in corsDomainWhitelist\" });\n\t\t\t}\n","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/MagicMirrorOrg/MagicMirror/blob/4b4a59534f7da01e4030e46029fe9dd649a7675e/js/server_functions.js#L67-L103","documentation":"The `/cors` proxy includes SSRF (Server-Side Request Forgery) protection in js/server_functions.js:85. It parses the target with `new URL(url)` and rejects, with HTTP 403 and this JSON error, anything that fails to parse or whose protocol is not exactly `http:` or `https:`. This prevents the mirror from being tricked into fetching internal/privileged resources (file://, ftp:, unix sockets, malformed URLs) on behalf of a client.","triggerScenarios":"A `/cors?url=...` request whose target: fails `new URL()` construction (missing scheme, spaces, invalid characters, relative URL); uses a non-HTTP scheme such as `file:///etc/passwd`, `ftp://`, or `data:`; or is otherwise unparseable, triggering the `catch` branch that logs `SSRF blocked (invalid URL)`. Further checks downstream (private/reserved IP ranges) use the same 403 message.","commonSituations":"A module configured with a target URL missing its `https://` prefix (e.g. `url=example.com/feed`); an attacker or mischievous link probing `?url=file:///etc/passwd` during a security scan; embedded whitespace or newline characters in a copied URL; IPv6 or custom-scheme feed URLs that a module passes straight through.","solutions":["Ensure the proxied target is a fully-qualified absolute URL starting with `http://` or `https://` (e.g. `https://example.com/feed.xml`).","Trim whitespace/newlines and percent-encode the target when building the request: `/cors?url=` + encodeURIComponent(target.trim()).","If the target host is on a private/reserved network (same message), move the resource to a public endpoint or run the fetch server-side in a node_helper instead of via the proxy.","Do not attempt to bypass the SSRF checks; if you need internal resources, proxy them through your own backend service with explicit allowlisting."],"exampleFix":"// before — module passes a scheme-less URL into the proxy\nfetch(`/cors?url=${feedUrl}`) // feedUrl = 'example.com/feed'\n// after — normalize and validate before proxying\nconst full = /^https?:\\/\\//i.test(feedUrl) ? feedUrl : 'https://' + feedUrl;\nfetch(`/cors?url=${encodeURIComponent(full.trim())}`)","handlingStrategy":"validation","validationCode":"function isSafeProxiableUrl(target) {\n  try {\n    const u = new URL(typeof target === 'string' ? target.trim() : '');\n    return u.protocol === 'http:' || u.protocol === 'https:';\n  } catch {\n    return false;\n  }\n}\n// call before requesting: if (!isSafeProxiableUrl(feedUrl)) { fix config }\n","typeGuard":"function isHttpUrl(value) {\n  if (typeof value !== 'string') return false;\n  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  if (!isHttpUrl(target)) throw new TypeError('target must be an absolute http(s) URL');\n  const res = await fetch(`/cors?url=${encodeURIComponent(target)}`);\n  if (res.status === 403) {\n    const body = await res.json().catch(() => ({}));\n    throw new Error(body.error ?? 'proxied request forbidden (SSRF guard or private address)');\n  }\n  return await res.text();\n} catch (err) {\n  console.error('cors proxy blocked the target URL', err);\n}","preventionTips":["Validate every module-provided URL is absolute http(s) with `new URL()` before sending it to /cors","Never attempt to proxy file://, ftp://, data:, or relative URLs through the mirror","Trim copied URLs to remove stray whitespace and newlines before encoding","Mirror the server's check client-side (parse + protocol test) so failures are caught before the request","For private/internal resources, use a server-side node_helper fetch rather than the public proxy"],"tags":["ssrf","http-403","url-validation","security","proxy"],"backgroundTag":"ssrf-blocked-url","analyzedSha":"4b4a59534f7da01e4030e46029fe9dd649a7675e","analyzedAt":"2026-08-31T21:49:42.591Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}