MagicMirrorOrg/MagicMirror · warning
Forbidden: domain not in corsDomainWhitelist
Error message
Forbidden: domain not in corsDomainWhitelist
What it means
When MagicMirror's `cors` config is set to "allowWhitelist", the /cors proxy only forwards requests whose target hostname appears in the `corsDomainWhitelist` array (compared lowercase). Any other domain is refused with 403 'domain not in corsDomainWhitelist'. This is an intentional configuration gate, not a network failure.
Source
Thrown at js/server_functions.js:101
} 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" });
}
const headersToSend = getHeadersToSend(req.url);
const expectedReceivedHeaders = geExpectedReceivedHeaders(req.url);
Log.log(`cors url: ${url}`);
// Resolve DNS once and validate the IP. The validated IP is then pinned
// for the actual connection so fetch() cannot re-resolve to a different
// address. This prevents DNS rebinding / TOCTOU attacks (GHSA-xhvw-r95j-xm4v).
const { address, family } = await dns.promises.lookup(parsed.hostname);
if (ipaddr.process(address).range() !== "unicast") {
Log.warn(`SSRF blocked: ${url}`);
return res.status(403).json({ error: "Forbidden: private or reserved addresses are not allowed" });
}
// Pin the validated IP — fetch() reuses it instead of doing its own DNS lookup
const dispatcher = new undici.Agent({
connect: {View on GitHub (pinned to 4b4a59534f)
Solutions
- Add the exact bare hostname to corsDomainWhitelist in config.js, e.g. corsDomainWhitelist: ["example.com"]
- Whitelist entries are hostname-only and matched after toLowerCase — do not include scheme, path, or uppercase letters
- Alternatively set cors: "allowAll" in config.js if you accept the security tradeoff
- If the target legitimately redirects to another host, whitelist the redirected hostname too
Example fix
// before (config.js) cors: "allowWhitelist", corsDomainWhitelist: ["https://calendar.example.com/feed.ics"] // after cors: "allowWhitelist", corsDomainWhitelist: ["calendar.example.com"]
Defensive patterns
Strategy: validation
Validate before calling
const host = new URL(feedUrl).hostname.toLowerCase();
if (window.MM_CONFIG_whitelist && !window.MM_CONFIG_whitelist.includes(host)) {
console.warn(`${host} missing from corsDomainWhitelist in config.js`);
} Type guard
function isWhitelisted(u, whitelist) {
try { return whitelist.map(h => h.toLowerCase()).includes(new URL(u).hostname.toLowerCase()); } catch { return false; }
} Try / catch
const res = await fetch(proxyUrl);
if (res.status === 403 && (await res.json()).error.includes("corsDomainWhitelist")) {
throw new Error(`Add ${new URL(feedUrl).hostname} to corsDomainWhitelist in config.js`);
} Prevention
- Store whitelist entries as bare lowercase hostnames only (no scheme/path)
- After enabling allowWhitelist, test every module's feed once
- Whitelist all hosts a feed may redirect to
- Keep whitelist in one place in config.js with comments
When it happens
Trigger: config.js contains `cors: "allowWhitelist"` and a module requests GET /cors?url=https://some-domain.com/... where some-domain.com (lowercased hostname) is not an entry of `corsDomainWhitelist`.
Common situations: Switching from default/allowAll to allowWhitelist without updating the whitelist; adding 'https://example.com/path' (full URL) or 'WWW.Example.com' (case) to the whitelist instead of the bare lowercase hostname 'example.com'; a feed changing domains (e.g. redirect to a CDN).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- This device is not allowed to access your mirror. <br> Pleas
- CORS proxy is disabled
- <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8
- Forbidden: private or reserved addresses are not allowed
- ${error.message}
AI-assisted analysis of MagicMirrorOrg/MagicMirror@4b4a59534f (2026-08-31).
Data as JSON: /api/errors/2e120b8e9aa383a3.
Report an issue: GitHub.