grpc/grpc-go · error

"headers" : unsupported "key

Error message

"headers" %d: unsupported "key" %s

What it means

Returned by parseHeaders (rbac_translator.go:237) when a header key is not matchable: keys starting with ':' (pseudo-headers), keys with the 'grpc-' prefix, or keys in the unsupportedHeaders set {host, connection, keep-alive, proxy-authenticate, proxy-authorization, te, trailer, transfer-encoding, upgrade} (defined at line 213). gRPC forbids RBAC matching on these because they are hop-by-hop, transport-managed, or reserved.

Solutions

  1. Remove the unsupported header from the rule, or match on an allowed custom metadata key instead (a non-reserved, non-grpc-prefixed header).
  2. If you need caller identity, use source.principals (mTLS peer) rather than a header.
  3. Re-read the unsupportedHeaders list and pseudo-header rules and audit all header keys in the policy.

Example fix

// before
"headers": [ { "key": "host", "values": ["api.example.com"] } ]

// after
"headers": [ { "key": "x-envoy-original-host", "values": ["api.example.com"] } ]
// or remove the header matcher and match on request.paths / source.principals
Defensive patterns

Strategy: validation

Validate before calling

var unsupportedHeaders = map[string]bool{
    "host": true, "connection": true, "keep-alive": true,
    "proxy-authenticate": true, "proxy-authorization": true,
    "te": true, "trailer": true, "transfer-encoding": true, "upgrade": true,
}
func allowedHeader(key string) bool {
    k := strings.ToLower(key)
    if k == "" || k[0] == ':' || strings.HasPrefix(k, "grpc-") {
        return false
    }
    return !unsupportedHeaders[k]
}

Try / catch

interceptor, err := authz.NewStatic(policyJSON)
if err != nil {
    if strings.Contains(err.Error(), `unsupported "key"`) {
        // remove or replace the reserved header in the policy
    }
}

Prevention

When it happens

Trigger: A policy rule whose request.headers[].key is one of the reserved/unsupported names (case-insensitive), e.g. "host", ":path", "grpc-trace-bin", "connection".

Common situations: Trying to authorize on the Host header; matching on grpc-* metadata; copying Envoy HTTP route logic into a gRPC policy without adjusting for reserved headers.

Related errors


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

Appendix: source

Thrown at authz/rbac_translator.go:237

	"te":                  true,
	"trailer":             true,
	"transfer-encoding":   true,
	"upgrade":             true,
}

func unsupportedHeader(key string) bool {
	return key[0] == ':' || strings.HasPrefix(key, "grpc-") || unsupportedHeaders[key]
}

func parseHeaders(headers []header) ([]*v3rbacpb.Permission, error) {
	hs := make([]*v3rbacpb.Permission, 0, len(headers))
	for i, header := range headers {
		if header.Key == "" {
			return nil, fmt.Errorf(`"headers" %d: "key" is not present`, i)
		}
		header.Key = strings.ToLower(header.Key)
		if unsupportedHeader(header.Key) {
			return nil, fmt.Errorf(`"headers" %d: unsupported "key" %s`, i, header.Key)
		}
		if len(header.Values) == 0 {
			return nil, fmt.Errorf(`"headers" %d: "values" is not present`, i)
		}
		values := parseHeaderValues(header.Key, header.Values)
		hs = append(hs, permissionOr(values))
	}
	return hs, nil
}

func parseRequest(request request) (*v3rbacpb.Permission, error) {
	var and []*v3rbacpb.Permission
	if len(request.Paths) > 0 {
		and = append(and, permissionOr(parsePaths(request.Paths)))
	}
	if len(request.Headers) > 0 {
		headers, err := parseHeaders(request.Headers)
		if err != nil {

View on GitHub (pinned to 0c51461d27)