CloakHQ/CloakBrowser · error · Error

GeoIP resolution failed: GeoIP database is unavailable

Error message

GeoIP resolution failed: GeoIP database is unavailable

What it means

An egress IP was discovered, but no MaxMind GeoIP database file path was supplied or found, so the lookup cannot proceed. The library throws because the offline Reader requires a local .mmdb file.

Source

Thrown at js/src/geoip.ts:118

  const deadline = deadlineFromTimeout(timeoutMs);

  // Exit IP (through proxy, or the machine's own public IP when proxyUrl is
  // falsy) is most accurate — gateway DNS may differ from exit. Resolved even
  // when the DB is unavailable: the IP does not need the DB, and dropping it on
  // a DB hiccup would let WebRTC fall back to the real IP behind a proxy while
  // the connection shows the proxy IP — a real deanonymization.
  let ip = await resolveExitIp(proxyUrl, remainingMs(deadline));
  // Hostname fallback only applies to a proxy; no proxy → echo services only
  if (!ip && proxyUrl && !deadlineExpired(deadline)) ip = await resolveProxyIp(proxyUrl);
  if (!ip || deadlineExpired(deadline)) {
    if (deadlineExpired(deadline)) {
      throw new Error(`GeoIP resolution timed out after ${timeoutMs / 1000}s`);
    }
    throw new Error("GeoIP resolution failed: could not discover the egress IP");
  }

  if (!dbPath) {
    throw new Error("GeoIP resolution failed: GeoIP database is unavailable");
  }

  try {
    const buf = fs.readFileSync(dbPath);
    const reader = new Reader(buf);
    const result = reader.get(ip) as any;
    const timezone: string | null = result?.location?.time_zone ?? null;
    const countryCode: string | null = result?.country?.iso_code ?? null;
    const locale =
      countryCode ? (COUNTRY_LOCALE_MAP[countryCode] ?? null) : null;
    return { timezone, locale, exitIp: ip };
  } catch (error) {
    const detail = error instanceof Error ? error.message : String(error);
    throw new Error(`GeoIP lookup failed for ${ip}: ${detail}`, { cause: error });
  }
}

function getGeoipTimeoutMs(): number {

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Run downloadGeoipDb(dest) at startup and persist the resulting .mmdb path
  2. Pass the resolved dbPath into resolveProxyGeo / maybeResolveGeoip options
  3. Cache the downloaded database in CI or bundle it in the image so dbPath always exists
  4. Fall back to explicit timezone/locale when the DB cannot be provisioned

Example fix

// before
const geo = await resolveProxyGeo({ ip }); // no dbPath

// after
const dbPath = await ensureGeoipDb(); // downloads once, caches path
const geo = await resolveProxyGeo({ ip, dbPath });
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
function assertGeoipDb(dbPath?: string): string {
  if (!dbPath || !fs.existsSync(dbPath) || fs.statSync(dbPath).size === 0) {
    throw new Error('GeoIP DB missing — run downloadGeoipDb first');
  }
  return dbPath;
}

Type guard

function hasGeoipDb(dbPath?: string | null): dbPath is string {
  return typeof dbPath === 'string' && dbPath.length > 0;
}

Try / catch

try { geo = await resolveProxyGeo({ ip, dbPath }); }
catch (e) {
  if (e instanceof Error && e.message.includes('database is unavailable')) {
    await downloadGeoipDb(dest); geo = await resolveProxyGeo({ ip, dbPath: dest });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling resolveProxyGeo without dbPath (or with an empty path) when the GeoIP database has not been pre-downloaded, e.g. downloadGeoipDb was never run or its result was not wired into the call.

Common situations: Fresh installs that skipped the DB download step, CI pipelines that do not cache the .mmdb artifact, or a download that failed silently earlier leaving no file path.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/6941d66b5e5a3ee9. Report an issue: GitHub.