grpc/grpc-go · error

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

Error message

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

What it means

Returned by parseHeaders (rbac_translator.go:237) when a header matcher's key is unsupported: pseudo-headers (starting with ':'), 'grpc-' prefixed headers, or reserved HTTP/2 headers in the unsupportedHeaders set (connection, keep-alive, transfer-encoding, etc.). These headers cannot be matched by RBAC policy, so the translator rejects them.

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 03255a9237)

Solutions

  1. Remove or replace the unsupported header key with a permitted custom/application header.
  2. To match the request path or method, use the dedicated "paths" policy field rather than the ":path"/":method" pseudo-headers.
  3. Cross-check the key against the unsupportedHeaders list and the ':' / 'grpc-' rules.
  4. Use custom metadata (non-reserved) headers for authorization conditions.

Example fix

// before
{"headers":[{"key":":path","values":["/svc/M"]}]}

// after - use the paths field instead
{"paths":[{"path":"/svc/M"}]}
Defensive patterns

Strategy: validation

Validate before calling

func isAllowedHeaderKey(k string) bool {
    k = strings.ToLower(k)
    return k[0] != ':' && !strings.HasPrefix(k, "grpc-") && !unsupportedHeaders[k]
}

Try / catch

interceptor, err := authz.NewStatic(policyJSON)
if err != nil && strings.Contains(err.Error(), "unsupported") {
    return fmt.Errorf("policy uses a reserved header: %w", err)
}

Prevention

When it happens

Trigger: Writing a policy that tries to match on ":path", ":method", "grpc-status", "content-length", "te", "host", or any connection-specific header listed at rbac_translator.go:210-223.

Common situations: Attempting to route/authorize by pseudo-headers or gRPC metadata reserved names; copy-pasting Envoy header matchers that reference reserved headers.

Related errors


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