MagicMirrorOrg/MagicMirror · error

Forbidden: private or reserved addresses are not allowed

Error message

Forbidden: private or reserved addresses are not allowed

What it means

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.

Source

Thrown at js/server_functions.js:85

		if (!match) {
			url = `invalid url: ${req.url}`;
			Log.error(url);
			return res.status(400).send(url);
		} else {
			url = match[1];
			if (typeof global.config !== "undefined") {
				if (config.hideConfigSecrets) {
					url = replaceSecretPlaceholder(url);
				}
			}

			// Validate protocol before attempting connection (non-http/https are never allowed)
			let parsed;
			try {
				parsed = new URL(url);
			} catch {
				Log.warn(`SSRF blocked (invalid URL): ${url}`);
				return res.status(403).json({ error: "Forbidden: private or reserved addresses are not allowed" });
			}
			if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
				Log.warn(`SSRF blocked (protocol): ${url}`);
				return res.status(403).json({ error: "Forbidden: private or reserved addresses are not allowed" });
			}

			// Block localhost by hostname before even creating the dispatcher (no DNS needed).
			if (parsed.hostname.toLowerCase() === "localhost") {
				Log.warn(`SSRF blocked (localhost): ${url}`);
				return res.status(403).json({ error: "Forbidden: private or reserved addresses are not allowed" });
			}

			// Whitelist check: if enabled, only allow explicitly listed domains
			if (global.config.cors === "allowWhitelist" && !global.config.corsDomainWhitelist.includes(parsed.hostname.toLowerCase())) {
				Log.warn(`CORS blocked (not in whitelist): ${url}`);
				return res.status(403).json({ error: "Forbidden: domain not in corsDomainWhitelist" });
			}

View on GitHub (pinned to 4b4a59534f)

Solutions

  1. Ensure the proxied target is a fully-qualified absolute URL starting with `http://` or `https://` (e.g. `https://example.com/feed.xml`).
  2. Trim whitespace/newlines and percent-encode the target when building the request: `/cors?url=` + encodeURIComponent(target.trim()).
  3. 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.
  4. Do not attempt to bypass the SSRF checks; if you need internal resources, proxy them through your own backend service with explicit allowlisting.

Example fix

// before — module passes a scheme-less URL into the proxy
fetch(`/cors?url=${feedUrl}`) // feedUrl = 'example.com/feed'
// after — normalize and validate before proxying
const full = /^https?:\/\//i.test(feedUrl) ? feedUrl : 'https://' + feedUrl;
fetch(`/cors?url=${encodeURIComponent(full.trim())}`)
Defensive patterns

Strategy: validation

Validate before calling

function isSafeProxiableUrl(target) {
  try {
    const u = new URL(typeof target === 'string' ? target.trim() : '');
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch {
    return false;
  }
}
// call before requesting: if (!isSafeProxiableUrl(feedUrl)) { fix config }

Type guard

function isHttpUrl(value) {
  if (typeof value !== 'string') return false;
  try { const u = new URL(value); return u.protocol === 'http:' || u.protocol === 'https:'; }
  catch { return false; }
}

Try / catch

try {
  if (!isHttpUrl(target)) throw new TypeError('target must be an absolute http(s) URL');
  const res = await fetch(`/cors?url=${encodeURIComponent(target)}`);
  if (res.status === 403) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.error ?? 'proxied request forbidden (SSRF guard or private address)');
  }
  return await res.text();
} catch (err) {
  console.error('cors proxy blocked the target URL', err);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of MagicMirrorOrg/MagicMirror@4b4a59534f (2026-08-31). Data as JSON: /api/errors/82f566de92af812a. Report an issue: GitHub.