grpc/grpc-go · error

unknown header matcher type

Error message

unknown header matcher type

What it means

`newHeaderMatcher` (internal/xds/rbac/matchers.go:297) switches over the `HeaderMatchSpecifier` oneof of an Envoy HeaderMatcher proto. It handles exact, safe_regex, range, present, prefix, suffix, contains, and string_match variants. The default branch at line 324-325 returns this error when the oneof is unset or holds a variant unknown to gRPC.

Solutions

  1. Inspect the offending HeaderMatcher proto in the xDS response to identify which (or whether) a match variant is set.
  2. Replace the unsupported/empty header matcher with one of the supported variants (exact, prefix, suffix, contains, present, range, safe_regex, string_match).
  3. If a genuinely new Envoy matcher type is required, check for a newer grpc-go release that supports it; otherwise exclude that policy from gRPC-targeted configs.
  4. Ensure the header matcher is fully populated (the oneof is not left unset) in the control-plane-generated config.

Example fix

// before (HeaderMatcher with no variant / unsupported variant)
hm := &route_componentspb.HeaderMatcher{Name: "x-foo"} // HeaderMatchSpecifier == nil
m, err := newHeaderMatcher(hm) // err: unknown header matcher type

// after
hm := &route_componentspb.HeaderMatcher{
    Name: "x-foo",
    HeaderMatchSpecifier: &route_componentspb.HeaderMatcher_ExactMatch{ExactMatch: "bar"},
}
m, err := newHeaderMatcher(hm)
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate a HeaderMatcher proto before it reaches newHeaderMatcher.
func validateHeaderMatcher(hm *route_componentspb.HeaderMatcher) error {
    if hm == nil || hm.HeaderMatchSpecifier == nil {
        return fmt.Errorf("header matcher %q has no match variant set", hm.GetName())
    }
    switch hm.HeaderMatchSpecifier.(type) {
    case *route_componentspb.HeaderMatcher_ExactMatch,
        *route_componentspb.HeaderMatcher_SafeRegexMatch,
        *route_componentspb.HeaderMatcher_RangeMatch,
        *route_componentspb.HeaderMatcher_PresentMatch,
        *route_componentspb.HeaderMatcher_PrefixMatch,
        *route_componentspb.HeaderMatcher_SuffixMatch,
        *route_componentspb.HeaderMatcher_ContainsMatch,
        *route_componentspb.HeaderMatcher_StringMatch:
        return nil
    default:
        return fmt.Errorf("header matcher %q uses an unsupported type %T", hm.GetName(), hm.HeaderMatchSpecifier)
    }
}

Type guard

// Type-switch helper to confirm a HeaderMatcher variant is one gRPC understands.
func isSupportedHeaderMatcher(hm *route_componentspb.HeaderMatcher) bool {
    switch hm.HeaderMatchSpecifier.(type) {
    case *route_componentspb.HeaderMatcher_ExactMatch,
        *route_componentspb.HeaderMatcher_SafeRegexMatch,
        *route_componentspb.HeaderMatcher_RangeMatch,
        *route_componentspb.HeaderMatcher_PresentMatch,
        *route_componentspb.HeaderMatcher_PrefixMatch,
        *route_componentspb.HeaderMatcher_SuffixMatch,
        *route_componentspb.HeaderMatcher_ContainsMatch,
        *route_componentspb.HeaderMatcher_StringMatch:
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Triggered when an RBAC policy (or any xDS resource using HeaderMatcher) contains a header matcher whose `HeaderMatchSpecifier` is either nil (no variant selected) or a newer Envoy variant that gRPC has not implemented. The error is returned during RBAC chain-engine construction, which NACKs the resource.

Common situations: A control-plane/Envoy version that emits a header matcher type gRPC does not yet support; a malformed header matcher with no variant set (default-constructed proto); using Envoy-specific extensions in a policy delivered to gRPC; version skew between Envoy proto definitions and the gRPC-vendored ones.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/558b893153b8a198. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/rbac/matchers.go:325

		m = internalmatcher.NewHeaderRegexMatcher(headerMatcherConfig.Name, regex, headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_RangeMatch:
		m = internalmatcher.NewHeaderRangeMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetRangeMatch().Start, headerMatcherConfig.GetRangeMatch().End, headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_PresentMatch:
		m = internalmatcher.NewHeaderPresentMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPresentMatch(), headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_PrefixMatch:
		m = internalmatcher.NewHeaderPrefixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPrefixMatch(), headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_SuffixMatch:
		m = internalmatcher.NewHeaderSuffixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetSuffixMatch(), headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_ContainsMatch:
		m = internalmatcher.NewHeaderContainsMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetContainsMatch(), headerMatcherConfig.InvertMatch)
	case *v3route_componentspb.HeaderMatcher_StringMatch:
		sm, err := internalmatcher.StringMatcherFromProto(headerMatcherConfig.GetStringMatch())
		if err != nil {
			return nil, fmt.Errorf("invalid string matcher %+v: %v", headerMatcherConfig.GetStringMatch(), err)
		}
		m = internalmatcher.NewHeaderStringMatcher(headerMatcherConfig.Name, sm, headerMatcherConfig.InvertMatch)
	default:
		return nil, errors.New("unknown header matcher type")
	}
	return &headerMatcher{matcher: m}, nil
}

func (hm *headerMatcher) match(data *rpcData) bool {
	return hm.matcher.Match(data.md)
}

// urlPathMatcher matches on the URL Path of the incoming RPC. In gRPC, this
// logically maps to the full method name the RPC is calling on the server side.
// urlPathMatcher implements the matcher interface.
type urlPathMatcher struct {
	stringMatcher internalmatcher.StringMatcher
}

func newURLPathMatcher(pathMatcher *v3matcherpb.PathMatcher) (*urlPathMatcher, error) {
	stringMatcher, err := internalmatcher.StringMatcherFromProto(pathMatcher.GetPath())
	if err != nil {

View on GitHub (pinned to 0c51461d27)