nocodb/nocodb · error · SsrfBlockedHostError

Connection to internal hosts is not allowed

Error message

Connection to internal hosts is not allowed

What it means

Thrown by assertExternalDbHostAllowed when the external-database SSRF guard sees a hostname that is literally a non-routable sentinel: '0.0.0.0', '::', or any case variant of 'localhost'. This is the literal-hostname branch — it fires before any DNS lookup, so even a hosts-file remap will not bypass it. The guard is enabled unless NC_DISABLE_SSRF_PROTECTION=true, NC_ALLOW_LOCAL_EXTERNAL_DBS=true (self-hosted only), or the backend forced it off; on cloud, forceEnforce is always on.

Source

Thrown at packages/noco-integrations/core/src/utils/externalDbSsrf.ts:87

}

/**
 * Throws `SsrfBlockedHostError` if `host` resolves to a non-routable range.
 * No-op when SSRF protection is disabled (see `isDbSsrfProtectionEnabled`).
 */
export async function assertExternalDbHostAllowed(
  host: unknown,
): Promise<void> {
  if (!isDbSsrfProtectionEnabled()) return;
  if (typeof host !== 'string' || host.length === 0) return;

  const trimmed = host.trim();
  if (
    trimmed === '0.0.0.0' ||
    trimmed === '::' ||
    /^localhost$/i.test(trimmed)
  ) {
    throw new SsrfBlockedHostError();
  }

  // TOCTOU note: the driver re-resolves at connect-time; a controlled DNS
  // record with short TTL could flip between this lookup and the driver's
  // connect(). Mitigating fully requires passing the resolved IP to the
  // driver, which is per-driver wiring out of scope here.
  let resolvedIps: string[] = [];
  if (isIP(trimmed)) {
    resolvedIps = [trimmed];
  } else {
    try {
      const records = await dns.lookup(trimmed, { all: true });
      resolvedIps = records.map((r) => r.address);
    } catch {
      // Let the driver surface DNS failures.
      return;
    }
  }

View on GitHub (pinned to d3caaf4e89)

Solutions

  1. Point the integration at the actual reachable address (the database's private IP, a DNS name that resolves to it, or the loopback IP if you intentionally mean localhost AND you enable the bypass).
  2. On self-hosted only: set NC_ALLOW_LOCAL_EXTERNAL_DBS=true (preferred, scoped to external DBs) or NC_DISABLE_SSRF_PROTECTION=true (broader) and restart the server.
  3. If the DB lives on another machine, use its hostname or non-loopback IP — do not use 'localhost' as a synonym for 'the DB server'.
  4. On cloud this guard cannot be disabled; move the database to a routable address reachable from the cloud network.

Example fix

// before
new MssqlIntegration({ host: 'localhost', port: 1433, ... });
// -> SsrfBlockedHostError: Connection to internal hosts is not allowed

// after (self-hosted, intentional)
// .env: NC_ALLOW_LOCAL_EXTERNAL_DBS=true
new MssqlIntegration({ host: 'localhost', port: 1433, ... });
Defensive patterns

Strategy: validation

Validate before calling

import { isIP } from 'net';

function isLiteralInternalHost(host: string): boolean {
  const t = host.trim().toLowerCase();
  return t === 'localhost' || t === '0.0.0.0' || t === '::';
}

// before opening the connection
if (isLiteralInternalHost(host) && process.env.NC_ALLOW_LOCAL_EXTERNAL_DBS !== 'true') {
  throw new Error(`Refusing localhost/any-address host '${host}' under SSRF protection`);
}

Type guard

function isNonRoutableLiteral(host: unknown): host is string {
  return (
    typeof host === 'string' &&
    ['localhost', '0.0.0.0', '::'].includes(host.trim().toLowerCase())
  );
}

Try / catch

import { SsrfBlockedHostError } from '@noco-integrations/core/utils/externalDbSsrf';

try {
  await assertExternalDbHostAllowed(host);
  await openConnection(host);
} catch (err) {
  if (err instanceof SsrfBlockedHostError) {
    ui.warn('Localhost hosts are blocked by SSRF protection; ask your admin to enable NC_ALLOW_LOCAL_EXTERNAL_DBS or use a routable host.');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: An integration tries to open an external MSSQL/Postgres/MySQL/etc. connection where the configured host string equals 'localhost', '0.0.0.0', or '::' (IPv6 any). Also triggered by UI 'Test Connection' or workflow node setup that submits such a host while SSRF protection is enabled.

Common situations: Developer running NocoDB + a local database on the same host and pointing an integration at 'localhost'; a config export that used 0.0.0.0 as a wildcard bind address mistakenly reused as the connect target; an IPv6-only dev box with '::' as the host.

Related errors


AI-assisted analysis of nocodb/nocodb@d3caaf4e89 (2026-08-12). Data as JSON: /api/errors/ef7f08d7d41e445f. Report an issue: GitHub.