{"record":{"id":"931123c2f4850161","repo":"MagicMirrorOrg/MagicMirror","slug":"doctype-html-html-lang-en-head-meta-char","errorCode":null,"errorMessage":"<!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>","messagePattern":"<!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>","errorType":"http","errorClass":null,"httpStatus":404,"severity":"info","filePath":"js/server.js","lineNumber":97,"sourceCode":"\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tLog.error(\"Failed to start server:\", err);\n\t\t\t});\n\n\t\t\tserver.listen(port, config.address || \"localhost\");\n\n\t\t\tif (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {\n\t\t\t\tLog.warn(\"You're using a full whitelist configuration to allow for all IPs\");\n\t\t\t}\n\n\t\t\tapp.use(ipAccessControl(config.ipWhitelist));\n\t\t\tapp.use(helmet(config.httpHeaders));\n\t\t\tapp.use(\"/js\", express.static(__dirname));\n\n\t\t\tif (config.hideConfigSecrets) {\n\t\t\t\tapp.get(\"/config/config.env\", (req, res) => {\n\t\t\t\t\tres.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>\");\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tlet 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\"];\n\t\t\tfor (const value of Object.values(vendor)) {\n\t\t\t\tconst dirArr = value.split(\"/\");\n\t\t\t\tif (dirArr[0] === \"node_modules\") directories.push(`/${dirArr[0]}/${dirArr[1]}`);\n\t\t\t}\n\t\t\tconst uniqDirs = [...new Set(directories)];\n\n\t\t\tfor (const directory of uniqDirs) {\n\t\t\t\tapp.use(directory, express.static(path.resolve(global.root_path + directory)));\n\t\t\t}\n\n\t\t\tconst startUp = new Date();\n\t\t\tconst getStartup = (req, res) => res.send(startUp);\n\n\t\t\tconst getConfig = (req, res) => {","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/MagicMirrorOrg/MagicMirror/blob/4b4a59534f7da01e4030e46029fe9dd649a7675e/js/server.js#L79-L115","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before — client module fetching secrets over HTTP\nconst res = await fetch('/config/config.env');\n// after — use config passed from the server side via node_helper / config\nconst apiKey = this.config.apiKey; // injected from config.js, never over public HTTP","handlingStrategy":"fallback","validationCode":"async function loadEnvConfig() {\n  const res = await fetch('/config/config.env');\n  const text = await res.text();\n  if (res.status === 404 && text.includes('Cannot GET')) {\n    return null; // hideConfigSecrets is active; endpoint intentionally masked\n  }\n  return text;\n}","typeGuard":"function isSecretsMasked(response) {\n  return response !== null && typeof response === 'object' && response.status === 404 && typeof response.body === 'string' && response.body.includes('Cannot GET /config/config.env');\n}","tryCatchPattern":"try {\n  const env = await loadEnvConfig();\n  if (env === null) {\n    // fall back to values injected via module config / socket notifications\n    return this.config.apiKey;\n  }\n} catch (err) {\n  console.warn('config.env unavailable; using injected config', err);\n}","preventionTips":["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"],"tags":["http-404","secrets","security","configuration"],"backgroundTag":"endpoint-404-not-found","analyzedSha":"4b4a59534f7da01e4030e46029fe9dd649a7675e","analyzedAt":"2026-08-31T21:49:42.591Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}