projectdiscovery/nuclei · error

grpc target host cannot be empty

Error message

grpc target host cannot be empty

What it means

net.SplitHostPort succeeded but the host portion is empty: the target starts with ':' (e.g. ':443' or ':50051'). There is no hostname to enforce the network policy (denylists, RestrictLocalNetworkAccess) against, so dialTarget refuses to continue.

Source

Thrown at pkg/js/libs/grpc/invoke.go:48

	insecureSkipVerify bool
	serverName         string
	maxRecvMsgSize     int
}

// dialTarget builds a *grpc.ClientConn whose every connection is routed through
// nuclei's network policy. The host is validated up front and the actual dial
// is delegated to the execution's fastdialer via a custom context dialer, so
// IP/host denylists and RestrictLocalNetworkAccess are always enforced. The
// passthrough scheme guarantees the target is handed verbatim to our dialer
// (instead of gRPC's built in DNS resolver), keeping resolution and policy
// enforcement inside fastdialer.
func dialTarget(ctx context.Context, executionID, target string, cfg connConfig) (*grpc.ClientConn, error) {
	host, _, err := net.SplitHostPort(target)
	if err != nil {
		return nil, fmt.Errorf("invalid grpc target %q (expected host:port): %w", target, err)
	}
	if host == "" {
		return nil, fmt.Errorf("grpc target host cannot be empty")
	}
	if executionID == "" {
		return nil, fmt.Errorf("grpc: refusing to dial without executionId")
	}
	if !protocolstate.IsHostAllowed(executionID, host) {
		return nil, protocolstate.ErrHostDenied.Msgf(host)
	}
	dialers := protocolstate.GetDialersWithId(executionID)
	if dialers == nil || dialers.Fastdialer == nil {
		return nil, fmt.Errorf("grpc: dialers not initialized for executionId %q", executionID)
	}

	contextDialer := func(dialCtx context.Context, addr string) (net.Conn, error) {
		return dialers.Fastdialer.Dial(dialCtx, "tcp", addr)
	}

	var creds credentials.TransportCredentials
	if cfg.plaintext {

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Supply a concrete host/IP: new grpc.Client('grpc.acme.com:443')
  2. Guard template flow: only construct the Client when the extracted host is non-empty
  3. Validate host and port separately in config before joining them

Example fix

// before
const client = new grpc.Client(`:${port}`);

// after: skip when the extractor found no host
if (!host) { return; }
const client = new grpc.Client(`${host}:${port}`);
Defensive patterns

Strategy: validation

Validate before calling

const hostOf = (t) => /^\[.*\]/.test(t) ? t.slice(1, t.indexOf(']')) : String(t || '').split(':')[0];
if (!hostOf(target)) {
  // extracted host empty: skip or fail the input, do not construct the client
}

Type guard

const hasGrpcHost = (t) => !!t && !t.trim().startsWith(':') && /^[[\w.-]/.test(t.trim());

Try / catch

try { const c = new grpc.Client(target); }
catch (e) { if (/host cannot be empty/.test(e.message || '')) { /* fix the empty host variable */ } }

Prevention

When it happens

Trigger: new grpc.Client(':443'); building the target by concatenation when the host variable is empty: `${host}:443` with host=''; whitespace-only host; ports-only config strings.

Common situations: Template extractors producing an empty host (match group missing); environment config with the host key unset but the port set; YAML anchors defaulting host to ''.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/a6aa9d80a572ef2f. Report an issue: GitHub.