projectdiscovery/nuclei · error

grpc: refusing to dial without executionId

Error message

grpc: refusing to dial without executionId

What it means

dialTarget refuses to connect when the executionId is empty. The grpc.Client captures executionID from the goja runtime at construction (nj.ExecutionId()); if nuclei never seeded the runtime or context with an execution id — i.e. the library is used outside a scan execution — every dial fails closed rather than bypassing network policy.

Source

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

}

// 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 {
		creds = insecure.NewCredentials()
	} else {
		serverName := cfg.serverName

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Run the gRPC code inside a nuclei code-protocol template so the engine attaches the executionId
  2. From Go/tests, seed the runtime/context with the execution id the same way nuclei does before constructing the Client
  3. Register protocolstate dialers for that same executionId, otherwise the next failure is 'dialers not initialized'

Example fix

// before: standalone test runtime, no execution bound to it
const client = new grpc.Client('grpc.acme.com:443');
client.Invoke('acme.v1.Svc/Get', '{}'); // -> refusing to dial without executionId

// after: same code runs as a nuclei code template (code protocol),
// which seeds the executionId on the goja runtime automatically
// template: code:
//   const grpc = require('nuclei/grpc');
//   const c = new grpc.Client('grpc.acme.com:443');
//   c.Invoke('acme.v1.Svc/Get', '{}');
Defensive patterns

Strategy: try-catch

Try / catch

let client;
try {
  client = new grpc.Client(target, opts);
  client.Connect();
} catch (e) {
  if (/without executionId/.test(e.message || '')) {
    // running outside a nuclei execution: move this code into a code-protocol template
  }
}

Prevention

When it happens

Trigger: Driving pkg/js/libs/grpc from standalone Go code or unit tests where the goja runtime/context carries no execution id; constructing a Client before the execution context was attached to the runtime; reusing a Client object across runtimes.

Common situations: SDK/embedded usage (lib/) that instantiates the JS libraries outside a real scan; developers testing Client.Invoke directly without nuclei's runtime initialization.

Related errors


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