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
- 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.
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
- 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
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- CORS proxy is disabled
- Failed to parse client IP: ${clientIp}
- This device is not allowed to access your mirror. <br> Pleas
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8
- invalid url: ${req.url}
AI-assisted analysis of MagicMirrorOrg/MagicMirror@4b4a59534f (2026-08-31).
Data as JSON: /api/errors/82f566de92af812a.
Report an issue: GitHub.