projectdiscovery/nuclei · error

failed to build descriptor source from protoset: %w

Error message

failed to build descriptor source from protoset: %w

What it means

grpcurl.DescriptorSourceFromFileDescriptorSet rejected the parsed FileDescriptorSet: typically the set does not contain the service/method the template will invoke, or required imported types are missing, so symbol resolution against the set cannot be built and later ListServices/Invoke calls would fail.

Source

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

	noop := func() {}
	if strings.TrimSpace(protosetFile) != "" {
		// resolve through the local-file-access allowlist: unless -lfa is set,
		// only files inside the nuclei-templates directory are permitted.
		normalized, err := protocolstate.NormalizePathWithExecutionId(executionID, protosetFile)
		if err != nil {
			return nil, noop, fmt.Errorf("protoset path denied: %w", err)
		}
		data, err := os.ReadFile(normalized)
		if err != nil {
			return nil, noop, fmt.Errorf("failed to read protoset file: %w", err)
		}
		fds := &descriptorpb.FileDescriptorSet{}
		if err := proto.Unmarshal(data, fds); err != nil {
			return nil, noop, fmt.Errorf("failed to parse protoset file: %w", err)
		}
		src, err := grpcurl.DescriptorSourceFromFileDescriptorSet(fds)
		if err != nil {
			return nil, noop, fmt.Errorf("failed to build descriptor source from protoset: %w", err)
		}
		return src, noop, nil
	}

	refClient := grpcreflect.NewClientAuto(ctx, cc)
	cleanup := func() { refClient.Reset() }
	return grpcurl.DescriptorSourceFromServer(ctx, refClient), cleanup, nil
}

// invokeUnary invokes a unary (or single-response) gRPC method described by src
// over cc, marshaling the JSON request and formatting the JSON response.
func invokeUnary(ctx context.Context, src grpcurl.DescriptorSource, cc *grpc.ClientConn, method, requestJSON string, headers []string) (string, error) {
	body := strings.TrimSpace(requestJSON)
	if body == "" {
		body = "{}"
	}
	var in io.Reader = strings.NewReader(body)

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Recompile including imports and every relevant schema: protoc --descriptor_set_out=acme.protoset --include_imports --proto_path=. acme/*.proto
  2. Verify coverage: after Connect(), call ListServices() and check the fully-qualified service name matches the Invoke method string
  3. If the target supports reflection, drop ProtosetFile and let the server provide its own schema

Example fix

# before: descriptor compiled without imports -> symbols unresolvable
protoc --descriptor_set_out=acme.protoset acme.proto

# after: include imports (and all files that define the service)
protoc --descriptor_set_out=acme.protoset --include_imports --proto_path=. acme.proto
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const c = new grpc.Client(t, o);
  c.Connect();
  const svc = c.ListServices(); // verify the service you plan to Invoke is present
} catch (e) {
  if (/descriptor source/.test(e.message || '')) {
    // recompile protoset with --include_imports, or omit ProtosetFile to use server reflection
  }
}

Prevention

When it happens

Trigger: Descriptor compiled without --include_imports so transitive message types are absent; invoking 'acme.v1.Svc/Get' while the protoset only defines acme.v2; merging multiple .proto files into one set with conflicting duplicate symbols.

Common situations: Protoset generated from a subset of the repo's .proto files; version drift between the descriptor the author compiled and the service actually deployed; renaming services between proto versions.

Related errors


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