{"record":{"id":"a318c07271eabe60","repo":"projectdiscovery/nuclei","slug":"invalid-grpc-target-q-expected-host-port-w","errorCode":null,"errorMessage":"invalid grpc target %q (expected host:port): %w","messagePattern":"invalid grpc target %q \\(expected host:port\\): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/js/libs/grpc/invoke.go","lineNumber":45,"sourceCode":"// logic stays independently testable.\ntype connConfig struct {\n\tplaintext          bool\n\tinsecureSkipVerify bool\n\tserverName         string\n\tmaxRecvMsgSize     int\n}\n\n// dialTarget builds a *grpc.ClientConn whose every connection is routed through\n// nuclei's network policy. The host is validated up front and the actual dial\n// is delegated to the execution's fastdialer via a custom context dialer, so\n// IP/host denylists and RestrictLocalNetworkAccess are always enforced. The\n// passthrough scheme guarantees the target is handed verbatim to our dialer\n// (instead of gRPC's built in DNS resolver), keeping resolution and policy\n// enforcement inside fastdialer.\nfunc dialTarget(ctx context.Context, executionID, target string, cfg connConfig) (*grpc.ClientConn, error) {\n\thost, _, err := net.SplitHostPort(target)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid grpc target %q (expected host:port): %w\", target, err)\n\t}\n\tif host == \"\" {\n\t\treturn nil, fmt.Errorf(\"grpc target host cannot be empty\")\n\t}\n\tif executionID == \"\" {\n\t\treturn nil, fmt.Errorf(\"grpc: refusing to dial without executionId\")\n\t}\n\tif !protocolstate.IsHostAllowed(executionID, host) {\n\t\treturn nil, protocolstate.ErrHostDenied.Msgf(host)\n\t}\n\tdialers := protocolstate.GetDialersWithId(executionID)\n\tif dialers == nil || dialers.Fastdialer == nil {\n\t\treturn nil, fmt.Errorf(\"grpc: dialers not initialized for executionId %q\", executionID)\n\t}\n\n\tcontextDialer := func(dialCtx context.Context, addr string) (net.Conn, error) {\n\t\treturn dialers.Fastdialer.Dial(dialCtx, \"tcp\", addr)\n\t}","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/projectdiscovery/nuclei/blob/265b3a3dec374741614e342f813c10f8b38d2bb7/pkg/js/libs/grpc/invoke.go#L27-L63","documentation":"grpc's dialTarget validates the target with net.SplitHostPort and it failed: the Client target must be plain host:port ('grpc.acme.com:443'). Scheme prefixes and port-less hosts are rejected up front so the passthrough resolver can hand the address verbatim to fastdialer, keeping DNS resolution and policy enforcement inside nuclei.","triggerScenarios":"new grpc.Client('grpc.acme.com') with no :443; passing 'grpc://grpc.acme.com:443' or 'https://grpc.acme.com:443'; unbracketed IPv6 such as '::1:443'; a target string with whitespace.","commonSituations":"Copy-pasting a URL from documentation into the Client constructor; template variables that carry a scheme; authors assuming grpc.Client behaves like http.Client (which requires a scheme — grpc is the opposite).","solutions":["Pass host:port only: new grpc.Client('grpc.acme.com:443', opts)","Strip any scheme before constructing: target = target.replace(/^\\w+:\\/\\//, '')","Bracket IPv6 targets: '[2001:db8::1]:443'"],"exampleFix":"// before\nconst client = new grpc.Client('grpc://grpc.acme.com:443');\n\n// after: plain host:port, no scheme\nconst client = new grpc.Client('grpc.acme.com:443');","handlingStrategy":"validation","validationCode":"const HOST_PORT = /^(?:\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):\\d+$/;\nfunction assertHostPort(t) {\n  if (!HOST_PORT.test(t)) throw new Error(`grpc target must be host:port, got: ${t}`);\n}\nassertHostPort(target);\nconst client = new grpc.Client(target, opts);","typeGuard":"const isHostPort = (t) => /^(?:\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):\\d+$/.test(String(t || ''));","tryCatchPattern":"try {\n  const c = new grpc.Client(target);\n} catch (e) {\n  if (/invalid grpc target/.test(e.message || '')) {\n    // strip scheme, add port, then reconstruct the client\n  }\n}","preventionTips":["Never include a scheme in the grpc.Client target","Always append the port (443 for TLS, 50051-style for h2c)","Bracket IPv6 literals in host:port targets"],"tags":["grpc","javascript","url-validation"],"backgroundTag":null,"analyzedSha":"265b3a3dec374741614e342f813c10f8b38d2bb7","analyzedAt":"2026-08-15T20:05:51.855Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}