{"record":{"id":"c065b7deaa21fd05","repo":"koala73/worldmonitor","slug":"imd-proxy-url-invalid","errorCode":null,"errorMessage":"IMD_PROXY_URL_INVALID","messagePattern":"IMD_PROXY_URL_INVALID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/imd-cyclone-marine.mjs","lineNumber":776,"sourceCode":"  if (apiToken) headers.Authorization = `Bearer ${apiToken}`;\n  const response = await fetchFn(url, {\n    headers,\n    redirect: 'error',\n    signal: AbortSignal.timeout(timeoutMs),\n  });\n  if (!response.ok) {\n    const err = new Error(`HTTP ${response.status}`);\n    err.httpStatus = response.status;\n    throw err;\n  }\n  return readBoundedJsonResponse(response, maxBytes);\n}\n\nexport function createImdProxyFetch(rawProxyUrl, { proxyFetchFn = proxyFetch } = {}) {\n  const proxyUrl = String(rawProxyUrl || '').trim();\n  if (!proxyUrl) throw new Error('IMD_PROXY_URL_MISSING');\n  const proxyConfig = parseProxyConfig(proxyUrl);\n  if (!proxyConfig || proxyConfig.tls !== true) throw new Error('IMD_PROXY_URL_INVALID');\n  return async (url, init = {}) => {\n    if (!isAllowedImdHost(url)) throw new Error('UNTRUSTED_SOURCE_HOST');\n    const headers = init.headers || {};\n    const response = await proxyFetchFn(url, proxyConfig, {\n      accept: headers.Accept || headers.accept || '*/*',\n      headers,\n      method: init.method || 'GET',\n      body: init.body ?? null,\n      maxResponseBytes: IMD_MAX_BYTES,\n      timeoutMs: IMD_TIMEOUT_MS,\n      signal: init.signal,\n    });\n    const responseHeaders = {};\n    if (response.contentType) responseHeaders['Content-Type'] = response.contentType;\n    if (response.location) responseHeaders.Location = response.location;\n    const status = Number(response.status) || 502;\n    const body = status === 204 || status === 205 || status === 304\n      ? null","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/lib/imd-cyclone-marine.mjs#L758-L794","documentation":"After a non-empty proxy URL is provided, createImdProxyFetch() parses it with parseProxyConfig() and requires the resulting config to exist with tls === true (an HTTPS proxy endpoint). If parsing fails or the proxy is not TLS, it throws IMD_PROXY_URL_INVALID. This guarantees the proxy connection is encrypted before any IMD traffic is sent through it.","triggerScenarios":"Passing a malformed URL (e.g. 'not-a-url'), a proxy URL using a scheme parseProxyConfig rejects, or an http:// (non-TLS) proxy URL such as 'http://proxy.internal:8080' — parseProxyConfig returns null/undefined or a config whose tls !== true.","commonSituations":"Configuring an internal plaintext HTTP proxy out of habit; pasting a SOCKS or socks5:// URL the parser does not support; a truncated/garbled value in the secret store; migrating config between environments where the scheme changed.","solutions":["Use an https:// proxy URL so parseProxyConfig yields tls === true (e.g. https://proxy.example.com:8443).","Log/inspect the raw env value to confirm it is a complete, well-formed URL with a supported scheme.","If only a plaintext HTTP proxy exists, front it with a TLS-terminating wrapper or use a different egress proxy; do not attempt to bypass the TLS requirement.","Check that no whitespace/quotes from the secret store leaked into the value."],"exampleFix":"// before\ncreateImdProxyFetch('http://proxy.internal:8080');\n// after\ncreateImdProxyFetch('https://proxy.internal:8443');","handlingStrategy":"validation","validationCode":"const proxyUrl = (process.env.IMD_PROXY_URL ?? '').trim();\nlet parsed;\ntry { parsed = new URL(proxyUrl); } catch { parsed = null; }\nif (!parsed || parsed.protocol !== 'https:') {\n  throw new Error(`IMD_PROXY_URL must be a well-formed https:// URL, got: ${proxyUrl}`);\n}\nconst imdFetch = createImdProxyFetch(proxyUrl);","typeGuard":"function isHttpsProxyUrl(v) {\n  if (typeof v !== 'string') return false;\n  try {\n    const u = new URL(v.trim());\n    return u.protocol === 'https:' && Boolean(u.hostname);\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  imdFetch = createImdProxyFetch(proxyUrl);\n} catch (err) {\n  if (err.message === 'IMD_PROXY_URL_INVALID') {\n    console.error(`IMD_PROXY_URL is not a valid TLS proxy endpoint: ${proxyUrl}`);\n    throw new Error('Fix IMD_PROXY_URL to an https:// proxy endpoint before starting');\n  }\n  throw err;\n}","preventionTips":["Store the proxy URL with the https:// scheme included in the secret store, not just host:port.","Validate the scheme at config-load time with a URL parse before the app starts.","Never configure plaintext http:// or SOCKS proxies for this path; the library requires TLS by design.","Watch for copied values with surrounding quotes/whitespace that break URL parsing."],"tags":["configuration","proxy","tls","invalid-url","imd"],"backgroundTag":"invalid-config-value","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}