{"record":{"id":"697316ec7b954d77","repo":"MagicMirrorOrg/MagicMirror","slug":"invalid-url-req-url","errorCode":null,"errorMessage":"invalid url: ${req.url}","messagePattern":"invalid url: (.+?)","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"js/server_functions.js","lineNumber":70,"sourceCode":" * Only the url-param of the input request url is required. It must be the last parameter.\n * @param {Request} req - the request\n * @param {Response} res - the result\n * @returns {Promise<void>} A promise that resolves when the response is sent\n */\nasync function cors (req, res) {\n\tif (global.config.cors === \"disabled\") {\n\t\tLog.error(\"CORS is disabled, you need to enable it in `config.js` by setting `cors` to `allowAll` or `allowWhitelist`\");\n\t\treturn res.status(403).json({ error: \"CORS proxy is disabled\" });\n\t}\n\tlet url;\n\ttry {\n\t\tconst urlRegEx = \"url=(.+?)$\";\n\n\t\tconst match = new RegExp(urlRegEx, \"g\").exec(req.url);\n\t\tif (!match) {\n\t\t\turl = `invalid url: ${req.url}`;\n\t\t\tLog.error(url);\n\t\t\treturn res.status(400).send(url);\n\t\t} else {\n\t\t\turl = match[1];\n\t\t\tif (typeof global.config !== \"undefined\") {\n\t\t\t\tif (config.hideConfigSecrets) {\n\t\t\t\t\turl = replaceSecretPlaceholder(url);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Validate protocol before attempting connection (non-http/https are never allowed)\n\t\t\tlet parsed;\n\t\t\ttry {\n\t\t\t\tparsed = new URL(url);\n\t\t\t} catch {\n\t\t\t\tLog.warn(`SSRF blocked (invalid URL): ${url}`);\n\t\t\t\treturn res.status(403).json({ error: \"Forbidden: private or reserved addresses are not allowed\" });\n\t\t\t}\n\t\t\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\t\t\tLog.warn(`SSRF blocked (protocol): ${url}`);","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/MagicMirrorOrg/MagicMirror/blob/4b4a59534f7da01e4030e46029fe9dd649a7675e/js/server_functions.js#L52-L88","documentation":"In the `/cors` proxy handler (js/server_functions.js:70), the URL is extracted with the regex `url=(.+?)$` applied to `req.url`. If the request path contains no `url=` parameter at all, no match is produced, and the handler returns HTTP 400 with the message `invalid url: <req.url>` after logging it via `Log.error`. Note the returned string is the literal template-interpolated request URL, not a fixed message.","triggerScenarios":"A request hits `/cors` (or `/cors?foo=bar`) without a `url=` query parameter; the URL parameter is misnamed (`?target=` or `?src=`); the query string is mangled so `url=` never appears in `req.url` (e.g. an HTTP client that strips the query, or a double-encoding/relative-URL mistake that yields `/cors` with no query at all); a trailing question mark or HTML form submit that drops the param.","commonSituations":"Hand-written fetch/curl calls to the proxy forgetting the `?url=` parameter; a module config where the target URL is empty/undefined so the template produces `/cors?url=undefined` is actually fine, but an empty-string interpolation or string-building bug drops the param entirely; clicking a bookmark to `/cors` directly.","solutions":["Include a properly encoded target in the query: request `/cors?url=` + encodeURIComponent(targetUrl).","Check the server log line `invalid url: ...` to see exactly what path was received and fix the client-side URL construction accordingly.","If the source URL is coming from module config, guard that the value is a non-empty string before building the proxy URL.","Watch for double encoding: the proxy itself parses the raw `req.url`, so encode the target once, not twice."],"exampleFix":"// before — missing/misspelled parameter\nfetch('/cors?target=' + target)\n// after\nfetch('/cors?url=' + encodeURIComponent(target))","handlingStrategy":"validation","validationCode":"function buildProxyUrl(baseUrl, target) {\n  if (typeof target !== 'string' || target.trim() === '') {\n    throw new TypeError('cors proxy target must be a non-empty string');\n  }\n  return `${baseUrl}/cors?url=${encodeURIComponent(target)}`;\n}","typeGuard":"function isProxiableTarget(target) {\n  return typeof target === 'string' && target.trim().length > 0 && /^https?:\\/\\//i.test(target.trim());\n}","tryCatchPattern":"try {\n  if (!isProxiableTarget(target)) throw new TypeError('missing or empty url parameter for /cors');\n  const res = await fetch(`/cors?url=${encodeURIComponent(target)}`);\n  if (res.status === 400) throw new Error(`/cors rejected request: ${await res.text()}`);\n  return await res.text();\n} catch (err) {\n  console.error('cors proxy request failed', err);\n}","preventionTips":["Always send the parameter named exactly `url=` and encode the target with encodeURIComponent","Validate the target is a non-empty absolute http(s) URL before building the request","Check module config for undefined/empty feed URLs that produce empty parameters","Read the server log's `invalid url: <req.url>` to debug exactly what was received","Unit-test URL construction helpers that build /cors requests"],"tags":["http-400","url-parsing","proxy","query-parameter"],"backgroundTag":"missing-query-parameter","analyzedSha":"4b4a59534f7da01e4030e46029fe9dd649a7675e","analyzedAt":"2026-08-31T21:49:42.591Z","schemaVersion":2},"datasetVersion":"2026-08-31T22:30:34.772Z"}