{"record":{"id":"6dc4f0e44e08d403","repo":"grpc/grpc-go","slug":"spiffe-bundlemapfrombytes-invalid-trust-domain","errorCode":null,"errorMessage":"spiffe: BundleMapFromBytes() invalid trust domain %q found when parsing SPIFFE Bundle Map: %v","messagePattern":"spiffe: BundleMapFromBytes\\(\\) invalid trust domain %q found when parsing SPIFFE Bundle Map: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/credentials/spiffe/spiffe.go","lineNumber":55,"sourceCode":"// BundleMapFromBytes parses bytes into a SPIFFE Bundle Map. See the\n// SPIFFE Bundle Map spec for more detail -\n// https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#4-spiffe-bundle-format\n// If duplicate keys are encountered in the JSON parsing, Go's default unmarshal\n// behavior occurs which causes the last processed entry to be the entry in the\n// parsed map.\nfunc BundleMapFromBytes(bundleMapBytes []byte) (map[string]*spiffebundle.Bundle, error) {\n\tvar result partialParsedSPIFFEBundleMap\n\tif err := json.Unmarshal(bundleMapBytes, &result); err != nil {\n\t\treturn nil, err\n\t}\n\tif result.Bundles == nil {\n\t\treturn nil, fmt.Errorf(\"spiffe: BundleMapFromBytes() no bundles parsed from spiffe bundle map bytes\")\n\t}\n\tbundleMap := map[string]*spiffebundle.Bundle{}\n\tfor td, jsonBundle := range result.Bundles {\n\t\ttrustDomain, err := spiffeid.TrustDomainFromString(td)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"spiffe: BundleMapFromBytes() invalid trust domain %q found when parsing SPIFFE Bundle Map: %v\", td, err)\n\t\t}\n\t\tbundle, err := spiffebundle.Parse(trustDomain, jsonBundle)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"spiffe: BundleMapFromBytes() failed to parse bundle for trust domain %q: %v\", td, err)\n\t\t}\n\t\tbundleMap[td] = bundle\n\t}\n\treturn bundleMap, nil\n}\n\n// GetRootsFromSPIFFEBundleMap returns the root trust certificates from the\n// SPIFFE bundle map for the given trust domain from the leaf certificate.\nfunc GetRootsFromSPIFFEBundleMap(bundleMap map[string]*spiffebundle.Bundle, leafCert *x509.Certificate) (*x509.CertPool, error) {\n\t// 1. Upon receiving a peer certificate, verify that it is a well-formed SPIFFE\n\t//    leaf certificate.  In particular, it must have a single URI SAN containing\n\t//    a well-formed SPIFFE ID ([SPIFFE ID format]).\n\tspiffeID, err := idFromCert(leafCert)\n\tif err != nil {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/credentials/spiffe/spiffe.go#L37-L73","documentation":"Returned when a key inside the parsed Bundle Map's 'trust_domains' object is not a valid SPIFFE trust domain name. Trust domain names are validated by spiffeid.TrustDomainFromString, which rejects empty strings, uppercase letters, and characters outside [a-z0-9._-]. The offending key (the trust domain string) and the underlying validation error are both surfaced.","triggerScenarios":"A Bundle Map JSON with a key like \"My Domain\", \"domain.example:443\", \"\", or any value containing spaces, colons, or capitals in the trust_domains map. The loop at spiffe.go:52 calls TrustDomainFromString on every key.","commonSituations":"Bundle generated by a non-conformant source; hand-edited JSON; a trust domain configured with a URL-like form (\"https://x\") instead of the bare name; upstream SPIRE misconfiguration that put a URI in the key.","solutions":["Inspect the trust_domains keys and correct any that contain invalid characters, uppercase, colons, or are empty.","Regenerate the Bundle Map from the issuing SPIRE/SPIFFE authority so keys are lowercased trust domain names.","Validate each key with a regex like ^[a-z0-9._-]+$ before constructing the Bundle Map bytes."],"exampleFix":"// before\n{\"trust_domains\": {\"Prod Domain\": {...}}}\n\n// after\n{\"trust_domains\": {\"prod-domain\": {...}}}","handlingStrategy":"validation","validationCode":"var trustDomainRE = regexp.MustCompile(`^[a-z0-9._-]+$`)\n\nfunc validateBundleMapKeys(b []byte) error {\n    var m map[string]json.RawMessage\n    if err := json.Unmarshal(b, &m); err != nil { return err }\n    td, ok := m[\"trust_domains\"]\n    if !ok { return errors.New(\"missing trust_domains\") }\n    var tds map[string]json.RawMessage\n    if err := json.Unmarshal(td, &tds); err != nil { return err }\n    for k := range tds {\n        if !trustDomainRE.MatchString(k) {\n            return fmt.Errorf(\"invalid trust domain key %q\", k)\n        }\n    }\n    return nil\n}","typeGuard":"func isValidTrustDomainName(s string) bool {\n    if s == \"\" { return false }\n    for _, r := range s {\n        if !(r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || r == '.' || r == '_' || r == '-') {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":null,"preventionTips":["Generate Bundle Maps from SPIRE rather than editing JSON by hand.","Lint trust domain names with a strict character class regex.","Pin trust domain names in config and compare against the loaded map at startup."],"tags":["grpc","spiffe","trust-domain","validation","json-parsing"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}