koala73/worldmonitor · error

IMD_PROXY_URL_INVALID

Error message

IMD_PROXY_URL_INVALID

What it means

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.

Solutions

  1. Use an https:// proxy URL so parseProxyConfig yields tls === true (e.g. https://proxy.example.com:8443).
  2. Log/inspect the raw env value to confirm it is a complete, well-formed URL with a supported scheme.
  3. 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.
  4. Check that no whitespace/quotes from the secret store leaked into the value.

Example fix

// before
createImdProxyFetch('http://proxy.internal:8080');
// after
createImdProxyFetch('https://proxy.internal:8443');
Defensive patterns

Strategy: validation

Validate before calling

const proxyUrl = (process.env.IMD_PROXY_URL ?? '').trim();
let parsed;
try { parsed = new URL(proxyUrl); } catch { parsed = null; }
if (!parsed || parsed.protocol !== 'https:') {
  throw new Error(`IMD_PROXY_URL must be a well-formed https:// URL, got: ${proxyUrl}`);
}
const imdFetch = createImdProxyFetch(proxyUrl);

Type guard

function isHttpsProxyUrl(v) {
  if (typeof v !== 'string') return false;
  try {
    const u = new URL(v.trim());
    return u.protocol === 'https:' && Boolean(u.hostname);
  } catch {
    return false;
  }
}

Try / catch

try {
  imdFetch = createImdProxyFetch(proxyUrl);
} catch (err) {
  if (err.message === 'IMD_PROXY_URL_INVALID') {
    console.error(`IMD_PROXY_URL is not a valid TLS proxy endpoint: ${proxyUrl}`);
    throw new Error('Fix IMD_PROXY_URL to an https:// proxy endpoint before starting');
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/c065b7deaa21fd05. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/imd-cyclone-marine.mjs:776

  if (apiToken) headers.Authorization = `Bearer ${apiToken}`;
  const response = await fetchFn(url, {
    headers,
    redirect: 'error',
    signal: AbortSignal.timeout(timeoutMs),
  });
  if (!response.ok) {
    const err = new Error(`HTTP ${response.status}`);
    err.httpStatus = response.status;
    throw err;
  }
  return readBoundedJsonResponse(response, maxBytes);
}

export function createImdProxyFetch(rawProxyUrl, { proxyFetchFn = proxyFetch } = {}) {
  const proxyUrl = String(rawProxyUrl || '').trim();
  if (!proxyUrl) throw new Error('IMD_PROXY_URL_MISSING');
  const proxyConfig = parseProxyConfig(proxyUrl);
  if (!proxyConfig || proxyConfig.tls !== true) throw new Error('IMD_PROXY_URL_INVALID');
  return async (url, init = {}) => {
    if (!isAllowedImdHost(url)) throw new Error('UNTRUSTED_SOURCE_HOST');
    const headers = init.headers || {};
    const response = await proxyFetchFn(url, proxyConfig, {
      accept: headers.Accept || headers.accept || '*/*',
      headers,
      method: init.method || 'GET',
      body: init.body ?? null,
      maxResponseBytes: IMD_MAX_BYTES,
      timeoutMs: IMD_TIMEOUT_MS,
      signal: init.signal,
    });
    const responseHeaders = {};
    if (response.contentType) responseHeaders['Content-Type'] = response.contentType;
    if (response.location) responseHeaders.Location = response.location;
    const status = Number(response.status) || 502;
    const body = status === 204 || status === 205 || status === 304
      ? null

View on GitHub (pinned to 7d06c8633d)