grpc/grpc-go · error

"headers" : "key" is not present

Error message

"headers" %d: "key" is not present

What it means

Returned by parseHeaders (rbac_translator.go:233) while translating an authorization policy to Envoy RBAC. Each entry in a rule's request.headers must have a non-empty key; when header.Key == "" the parser reports the zero-based index i of the offending header. This becomes part of policy parsing, so it surfaces through NewStatic / file-watcher loading as a policy parse error.

Solutions

  1. Add a non-empty "key" to the header entry at the reported index in the policy JSON.
  2. Run the policy through a JSON schema/linter for the gRPC authorization policy before deploying.
  3. Use the file watcher's reload (it keeps the previous good policy) so a bad edit does not break a running server.

Example fix

// before
"headers": [ { "values": ["token"] } ]

// after
"headers": [ { "key": "authorization", "values": ["token"] } ]
Defensive patterns

Strategy: validation

Validate before calling

// Validate a header matcher entry before relying on it.
func validHeader(h struct{ Key string; Values []string }) error {
    if h.Key == "" {
        return errors.New("header key required")
    }
    return nil
}

Try / catch

interceptor, err := authz.NewStatic(policyJSON)
if err != nil {
    if strings.Contains(err.Error(), `"key" is not present`) {
        // add "key" to the flagged header entry and reload
    }
}

Prevention

When it happens

Trigger: An authorization policy JSON where an element of allow_rules/deny_rules[].request.headers omits "key" or sets it to "", e.g. {"values":["x"]} with no key field.

Common situations: Hand-authored policy missing the key; templating that drops empty fields; copy-paste of a header block without the key.

Related errors


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

Appendix: source

Thrown at authz/rbac_translator.go:233

	"connection":          true,
	"keep-alive":          true,
	"proxy-authenticate":  true,
	"proxy-authorization": true,
	"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)))

View on GitHub (pinned to 0c51461d27)