MagicMirrorOrg/MagicMirror · info
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8
Error message
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Cannot GET /config/config.env</pre> </body> </html>
What it means
In js/server.js:97, when the config option `hideConfigSecrets` is enabled, MagicMirror registers a route that answers every GET to `/config/config.env` with a synthetic Express-style 404 page ('Cannot GET /config/config.env'). This is a deliberate decoy/shield: `config.env` may contain API keys and other secrets, so the server hides it behind a fake 'not found' response whenever secret-hiding is turned on. Seeing this error means you (or a tool) requested a resource that is intentionally masked.
Source
Thrown at js/server.js:97
return;
}
Log.error("Failed to start server:", err);
});
server.listen(port, config.address || "localhost");
if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
Log.warn("You're using a full whitelist configuration to allow for all IPs");
}
app.use(ipAccessControl(config.ipWhitelist));
app.use(helmet(config.httpHeaders));
app.use("/js", express.static(__dirname));
if (config.hideConfigSecrets) {
app.get("/config/config.env", (req, res) => {
res.status(404).send("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Error</title>\n</head>\n<body>\n<pre>Cannot GET /config/config.env</pre>\n</body>\n</html>");
});
}
let directories = ["/config", "/css", "/favicon.svg", "/defaultmodules", "/modules", "/node_modules/animate.css", "/node_modules/@fontsource", "/node_modules/@fortawesome", "/node_modules/suncalc", "/translations", "/tests/configs", "/tests/mocks"];
for (const value of Object.values(vendor)) {
const dirArr = value.split("/");
if (dirArr[0] === "node_modules") directories.push(`/${dirArr[0]}/${dirArr[1]}`);
}
const uniqDirs = [...new Set(directories)];
for (const directory of uniqDirs) {
app.use(directory, express.static(path.resolve(global.root_path + directory)));
}
const startUp = new Date();
const getStartup = (req, res) => res.send(startUp);
const getConfig = (req, res) => {View on GitHub (pinned to 4b4a59534f)
Solutions
- If you legitimately need the values, read `config/config.env` from the server-side filesystem or process environment instead of fetching the HTTP URL — the file exists, only the HTTP route is masked.
- Keep `hideConfigSecrets: true` if the goal is protecting secrets; do not disable it just to make a client fetch work, instead pass secrets to the client through the module's `socketNotification` payload.
- If this is a trusted, non-exposed development mirror and you deliberately want the env file served, set `hideConfigSecrets: false` in config.js and restart.
- Update any module or script that depends on fetching `/config/config.env` to use server-side config injection instead.
Example fix
// before — client module fetching secrets over HTTP
const res = await fetch('/config/config.env');
// after — use config passed from the server side via node_helper / config
const apiKey = this.config.apiKey; // injected from config.js, never over public HTTP Defensive patterns
Strategy: fallback
Validate before calling
async function loadEnvConfig() {
const res = await fetch('/config/config.env');
const text = await res.text();
if (res.status === 404 && text.includes('Cannot GET')) {
return null; // hideConfigSecrets is active; endpoint intentionally masked
}
return text;
} Type guard
function isSecretsMasked(response) {
return response !== null && typeof response === 'object' && response.status === 404 && typeof response.body === 'string' && response.body.includes('Cannot GET /config/config.env');
} Try / catch
try {
const env = await loadEnvConfig();
if (env === null) {
// fall back to values injected via module config / socket notifications
return this.config.apiKey;
}
} catch (err) {
console.warn('config.env unavailable; using injected config', err);
} Prevention
- Never fetch secrets over HTTP from the client; inject them through config.js and socketNotifications
- Treat `hideConfigSecrets: true` as a contract that /config/config.env will always 404
- Audit modules for hard-coded requests to /config/config.env before enabling hideConfigSecrets
- Keep real secrets in config.env on the server only, referenced by node_helper code
- Verify secret masking with a curl check after deployment: `curl -i http://mirror:8080/config/config.env` should return 404
When it happens
Trigger: Any GET request to the URL `/config/config.env` while `config.hideConfigSecrets` is true; module or tooling code that expects to fetch `/config/config.env` at runtime; automated secret scanners or curl checks probing the endpoint; a module authored against an older behavior where the env file was publicly served.
Common situations: A developer enables `hideConfigSecrets` to protect API keys and then notices their local script/dashboard can no longer read `/config/config.env`; a security audit probe reports 404 even though the file exists on disk; upgrading MagicMirror and custom code that fetched the env file over HTTP breaks because the route now always 404s.
Related errors
- This device is not allowed to access your mirror. <br> Pleas
- CORS proxy is disabled
- Forbidden: private or reserved addresses are not allowed
- Forbidden: domain not in corsDomainWhitelist
- [calendar] Your are using the deprecated config values 'colo
AI-assisted analysis of MagicMirrorOrg/MagicMirror@4b4a59534f (2026-08-31).
Data as JSON: /api/errors/931123c2f4850161.
Report an issue: GitHub.