projectdiscovery/nuclei · error

protoset path denied: %w

Error message

protoset path denied: %w

What it means

The ProtosetFile path was rejected by protocolstate.NormalizePathWithExecutionId: by default nuclei only permits local file reads inside the nuclei-templates directory; anything outside requires the -lfa (local file access) flag. This gate keeps JS templates from reading arbitrary files on the host running the scan.

Source

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

	if cfg.maxRecvMsgSize > 0 {
		opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(cfg.maxRecvMsgSize)))
	}
	_ = ctx // reserved for future dial-time hooks; connection is established lazily
	return grpc.NewClient("passthrough:///"+target, opts...)
}

// descriptorSource resolves the gRPC method/message schema either from a local
// compiled protoset file (read through the local-file-access allowlist) or, when
// no protoset is provided, from server reflection over the existing connection.
// The returned cleanup func must be called once the source is no longer needed.
func descriptorSource(ctx context.Context, executionID string, cc *grpc.ClientConn, protosetFile string) (grpcurl.DescriptorSource, func(), error) {
	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() }

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Place the protoset inside the nuclei-templates directory (or a subdirectory) and reference it with a path that stays under that root
  2. Run nuclei with -lfa when the descriptor must live outside the templates root
  3. Prefer server reflection: omit ProtosetFile entirely when the target supports gRPC reflection

Example fix

// before: absolute path outside the templates root -> denied without -lfa
const o = new grpc.Options();
o.ProtosetFile = '/opt/exploits/acme.protoset';

// after: ship the protoset with the template and keep the path under the templates root
o.ProtosetFile = 'grpc/acme.protoset';
// or: nuclei -lfa ... when the file must stay where it is
Defensive patterns

Strategy: validation

Validate before calling

function assertTemplateRelativePath(p) {
  if (!p || p.startsWith('/') || p.startsWith('..')) {
    throw new Error('protoset path must stay inside the templates root (or run nuclei with -lfa)');
  }
}
assertTemplateRelativePath(o.ProtosetFile);

Type guard

const isTemplatesRelativePath = (p) => !!p && !p.startsWith('/') && !p.startsWith('..');

Try / catch

try { const c = new grpc.Client(t, o); c.Connect(); }
catch (e) { if (/protoset path denied/.test(e.message || '')) { /* move file under templates root or enable -lfa */ } }

Prevention

When it happens

Trigger: opts.ProtosetFile = '/opt/exploits/acme.protoset' with -lfa unset; a relative path that resolves outside the templates root ('../../home/user/svc.protoset'); absolute paths to tooling directories.

Common situations: Template authors keeping protosets beside their tooling instead of shipping them with the template; CI running nuclei without -lfa while the author tested locally with it enabled; enterprise installs pinning templates to a custom directory the path does not account for.

Related errors


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