{"record":{"id":"bd3c3693be38bbcb","repo":"grpc/grpc-go","slug":"token-file-q-is-empty-w","errorCode":null,"errorMessage":"token file %q is empty: %w","messagePattern":"token file %q is empty: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/jwt/file_reader.go","lineNumber":57,"sourceCode":"}\n\n// jwtFileReader handles reading and parsing JWT tokens from files.\n// It is safe to call methods on this type concurrently as no state is stored.\ntype jwtFileReader struct {\n\ttokenFilePath string\n}\n\n// readToken reads and parses a JWT token from the configured file.\n// Returns the token string, expiration time, and any error encountered.\nfunc (r *jwtFileReader) readToken() (string, time.Time, error) {\n\ttokenBytes, err := os.ReadFile(r.tokenFilePath)\n\tif err != nil {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"%v: %w\", err, errTokenFileAccess)\n\t}\n\n\ttoken := strings.TrimSpace(string(tokenBytes))\n\tif token == \"\" {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"token file %q is empty: %w\", r.tokenFilePath, errJWTValidation)\n\t}\n\n\texp, err := r.extractExpiration(token)\n\tif err != nil {\n\t\treturn \"\", time.Time{}, fmt.Errorf(\"token file %q: %v: %w\", r.tokenFilePath, err, errJWTValidation)\n\t}\n\n\treturn token, exp, nil\n}\n\nconst tokenDelim = \".\"\n\n// extractClaimsRaw returns the JWT's claims part as raw string. Even though the\n// header and signature are not used, it still expects that the input string to\n// be well-formed (ie comprised of exactly three parts, separated by a dot\n// character).\nfunc extractClaimsRaw(s string) (string, bool) {\n\t_, s, ok := strings.Cut(s, tokenDelim)","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/jwt/file_reader.go#L39-L75","documentation":"After os.ReadFile succeeds the content is trimmed and, if it equals \"\", readToken returns 'token file %q is empty: %w' wrapping errJWTValidation (file_reader.go:55-58). The file exists and is readable but holds no usable token bytes.","triggerScenarios":"The token file exists but is zero-length or contains only whitespace/newlines. Common with a freshly created but not-yet-populated Secret, a mounted empty ConfigMap key, or a token-rotating sidecar that truncated the file mid-write when read.","commonSituations":"Projected service-account token volume not yet refreshed by kubelet on pod start, an init container that created the file but did not write content, a Secret referenced with the wrong data key (empty file created by mount), or a writer that opens with O_TRUNC and the reader races the write.","solutions":["Ensure whatever writes the token file atomically renames a fully-written temp file into place so readers never see empty content.","Delay process start until the token file is non-empty (e.g. a readiness check on the mounted volume).","Re-check the Secret/ConfigMap data key name matches what is written.","Log the file size at startup to catch zero-length mounts quickly."],"exampleFix":"// before: writer truncates then writes; reader sees empty\nos.WriteFile(path, []byte(\"\"), 0o600)\n\n// after: atomic write via temp + rename\ntmp := path + \".tmp\"\nos.WriteFile(tmp, []byte(tok), 0o600)\nos.Rename(tmp, path)","handlingStrategy":"validation","validationCode":"// Reject an empty token file at startup.\nfi, err := os.Stat(tokenFilePath)\nif err != nil { return err }\nif fi.Size() == 0 {\n    return fmt.Errorf(\"token file %q is empty\", tokenFilePath)\n}","typeGuard":"func isTokenFileEmptyErr(err error) bool {\n    return errors.Is(err, errJWTValidation) && strings.Contains(err.Error(), \"is empty\")\n}","tryCatchPattern":"_, _, err := r.readToken()\nif err != nil && strings.Contains(err.Error(), \"is empty\") {\n    // writer has not populated the file yet; wait for non-zero size then retry.\n    return err\n}","preventionTips":["Have writers atomically rename a fully-written temp file into place.","Delay process start until the token volume is non-empty (readiness probe).","Double-check the Secret/ConfigMap data key name.","Log the token file size at startup."],"tags":["grpc","jwt","filesystem","configuration","credentials"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}