{"record":{"id":"bbc9e1c52b9019f5","repo":"grpc/grpc-go","slug":"headers-d-key-is-not-present","errorCode":null,"errorMessage":"\"headers\" %d: \"key\" is not present","messagePattern":"\"headers\" (.+?): \"key\" is not present","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"authz/rbac_translator.go","lineNumber":233,"sourceCode":"\t\"connection\":          true,\n\t\"keep-alive\":          true,\n\t\"proxy-authenticate\":  true,\n\t\"proxy-authorization\": true,\n\t\"te\":                  true,\n\t\"trailer\":             true,\n\t\"transfer-encoding\":   true,\n\t\"upgrade\":             true,\n}\n\nfunc unsupportedHeader(key string) bool {\n\treturn key[0] == ':' || strings.HasPrefix(key, \"grpc-\") || unsupportedHeaders[key]\n}\n\nfunc parseHeaders(headers []header) ([]*v3rbacpb.Permission, error) {\n\ths := make([]*v3rbacpb.Permission, 0, len(headers))\n\tfor i, header := range headers {\n\t\tif header.Key == \"\" {\n\t\t\treturn nil, fmt.Errorf(`\"headers\" %d: \"key\" is not present`, i)\n\t\t}\n\t\theader.Key = strings.ToLower(header.Key)\n\t\tif unsupportedHeader(header.Key) {\n\t\t\treturn nil, fmt.Errorf(`\"headers\" %d: unsupported \"key\" %s`, i, header.Key)\n\t\t}\n\t\tif len(header.Values) == 0 {\n\t\t\treturn nil, fmt.Errorf(`\"headers\" %d: \"values\" is not present`, i)\n\t\t}\n\t\tvalues := parseHeaderValues(header.Key, header.Values)\n\t\ths = append(hs, permissionOr(values))\n\t}\n\treturn hs, nil\n}\n\nfunc parseRequest(request request) (*v3rbacpb.Permission, error) {\n\tvar and []*v3rbacpb.Permission\n\tif len(request.Paths) > 0 {\n\t\tand = append(and, permissionOr(parsePaths(request.Paths)))","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/authz/rbac_translator.go#L215-L251","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-authored policy missing the key; templating that drops empty fields; copy-paste of a header block without the key.","solutions":["Add a non-empty \"key\" to the header entry at the reported index in the policy JSON.","Run the policy through a JSON schema/linter for the gRPC authorization policy before deploying.","Use the file watcher's reload (it keeps the previous good policy) so a bad edit does not break a running server."],"exampleFix":"// before\n\"headers\": [ { \"values\": [\"token\"] } ]\n\n// after\n\"headers\": [ { \"key\": \"authorization\", \"values\": [\"token\"] } ]","handlingStrategy":"validation","validationCode":"// Validate a header matcher entry before relying on it.\nfunc validHeader(h struct{ Key string; Values []string }) error {\n    if h.Key == \"\" {\n        return errors.New(\"header key required\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"interceptor, err := authz.NewStatic(policyJSON)\nif err != nil {\n    if strings.Contains(err.Error(), `\"key\" is not present`) {\n        // add \"key\" to the flagged header entry and reload\n    }\n}","preventionTips":["Require a non-empty key on every header matcher in policy authoring.","Lint the policy JSON before deploying.","Use file-watcher reload to keep the old policy while fixing a bad edit."],"tags":["grpc","authz","rbac","policy","config","go"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}