{"record":{"id":"a7ff7896b7edd1e4","repo":"actualbudget/actual","slug":"invalid-url-parameter","errorCode":null,"errorMessage":"Invalid url parameter","messagePattern":"Invalid url parameter","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/sync-server/src/app-cors-proxy.js","lineNumber":145,"sourceCode":"  }\n\n  const targetUrlString = req.query.url;\n\n  if (!targetUrlString) {\n    return res.status(400).json({ error: 'Missing url parameter' });\n  }\n\n  // Validate session/token\n  const session = await validateSession(req, res);\n  if (!session) {\n    return; // validateSession already sent the response\n  }\n\n  let url;\n  try {\n    url = new URL(targetUrlString);\n  } catch {\n    return res.status(400).json({ error: 'Invalid url parameter' });\n  }\n\n  // Fetch the latest allowlist\n  try {\n    await fetchAllowlist();\n  } catch (error) {\n    console.error('Failed to fetch allowlist:', error);\n    return res.status(403).json({\n      error: 'URL not allowed',\n      message: 'Unable to verify allowlist',\n    });\n  }\n\n  // Check if the URL is allowed\n  if (!isUrlAllowed(url.href)) {\n    console.warn('Blocked request to unauthorized URL:', url.href);\n    return res.status(403).json({\n      error: 'URL not allowed',","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-cors-proxy.js#L127-L163","documentation":"The proxy parses the supplied url query parameter with `new URL(...)`. If parsing throws (malformed URL), the proxy returns 400 'Invalid url parameter'. This catches syntactically invalid targets before any network work or allowlist checks happen.","triggerScenarios":"Passing a string without a scheme (e.g. url=example.com/list.json), a url with illegal characters from incomplete encoding, or a truncated/garbled query value.","commonSituations":"Forgetting the https:// scheme; double-encoding or not encoding the target so special characters (&, ?, spaces) corrupt the value; concatenating base URLs and paths incorrectly.","solutions":["Ensure the target is an absolute URL including the scheme (https://...) before encoding it into the query string.","Run `new URL(target)` client-side to validate, and use encodeURIComponent when building the query.","Log the raw url query value received by the server to spot encoding corruption."],"exampleFix":"// before\nconst q = `/cors-proxy?url=${target}`; // unencoded\n// after\nconst u = new URL(target); // throws early if invalid\nconst q = `/cors-proxy?url=${encodeURIComponent(u.href)}`;","handlingStrategy":"validation","validationCode":"let parsed;\ntry { parsed = new URL(target); } catch {\n  throw new Error(`Invalid proxy target: ${target}`);\n}\nif (!/^https?:$/.test(parsed.protocol)) throw new Error('Only http(s) targets are supported');\nconst proxied = `/cors-proxy?url=${encodeURIComponent(parsed.href)}`;","typeGuard":"function isAbsoluteHttpUrl(s) {\n  try { const u = new URL(s); return u.protocol === 'http:' || u.protocol === 'https:'; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  return await proxy(target);\n} catch (e) {\n  if (e.status === 400 && /Invalid url/.test(e.message)) {\n    console.error(`Target '${target}' is not a valid absolute URL`);\n  }\n  throw e;\n}","preventionTips":["Always include the scheme (https://) in proxy targets.","Validate with `new URL()` client-side before sending.","Encode once — avoid double-encoding the target URL."],"tags":["cors-proxy","validation","url-parsing"],"backgroundTag":"invalid-url","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}