{"record":{"id":"c3a60e55c3aa5db8","repo":"grpc/grpc-go","slug":"spiffe-bundlemapfrombytes-no-bundles-parsed-fro","errorCode":null,"errorMessage":"spiffe: BundleMapFromBytes() no bundles parsed from spiffe bundle map bytes","messagePattern":"spiffe: BundleMapFromBytes\\(\\) no bundles parsed from spiffe bundle map bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/credentials/spiffe/spiffe.go","lineNumber":49,"sourceCode":")\n\ntype partialParsedSPIFFEBundleMap struct {\n\tBundles map[string]json.RawMessage `json:\"trust_domains\"`\n}\n\n// 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.","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/credentials/spiffe/spiffe.go#L31-L67","documentation":"Returned by spiffe.BundleMapFromBytes when the JSON document either is not an object containing a 'trust_domains' key or that key resolves to null. Per the SPIFFE Bundle Map spec the top-level shape must be {\"trust_domains\": {\"<td>\": {<bundle>}, ...}}, so an entirely missing or empty trust_domains map yields this error. It signals the input is structurally not a SPIFFE Bundle Map.","triggerScenarios":"Passing bytes that are a single SPIFFE Bundle (not a Bundle Map), a PEM certificate blob, an empty byte slice whose JSON is '{}', or a misnamed key like \"bundles\" instead of \"trust_domains\". The check is `if result.Bundles == nil` after json.Unmarshal into partialParsedSPIFFEBundleMap.","commonSituations":"Confusing SPIFFE Bundle (one trust domain) with SPIFFE Bundle Map (many); loading the wrong file from a SPIRE workload agent; fetching from a bundle endpoint that returns a single bundle; version skew between the bundle producer and this consumer.","solutions":["Confirm the input is a Bundle Map by checking for the top-level \"trust_domains\" object key.","If you have a single-domain bundle, wrap it: build the map {\"trust_domains\": {\"<domain>\": <bundle-json>}} before passing it in.","Fetch from a SPIFFE Bundle Map endpoint rather than a per-domain bundle endpoint.","Dump the first bytes and validate it parses as JSON with the expected schema before calling BundleMapFromBytes."],"exampleFix":"// before\nmap, err := spiffe.BundleMapFromBytes(singleBundleBytes) // wrong shape\n\n// after\nwrapped := fmt.Sprintf(`{\"trust_domains\":{\"%s\":%s}}`, td, string(singleBundleBytes))\nmap, err := spiffe.BundleMapFromBytes([]byte(wrapped))","handlingStrategy":"validation","validationCode":"func isSPIFFEBundleMap(b []byte) error {\n    var probe struct{ TrustDomains json.RawMessage `json:\"trust_domains\"` }\n    if err := json.Unmarshal(b, &probe); err != nil { return err }\n    if len(probe.TrustDomains) == 0 || string(probe.TrustDomains) == \"null\" {\n        return errors.New(\"not a SPIFFE Bundle Map: missing or null trust_domains\")\n    }\n    return nil\n}","typeGuard":"func looksLikeBundleMap(b []byte) bool {\n    var probe struct{ TrustDomains json.RawMessage `json:\"trust_domains\"` }\n    return json.Unmarshal(b, &probe) == nil && len(probe.TrustDomains) > 0 && string(probe.TrustDomains) != \"null\"\n}","tryCatchPattern":"bm, err := spiffe.BundleMapFromBytes(raw)\nif err != nil {\n    if strings.Contains(err.Error(), \"no bundles parsed\") {\n        // log input length/preview and re-fetch from the bundle endpoint\n    }\n    return err\n}","preventionTips":["Fetch Bundle Maps only from SPIFFE Bundle Map federation endpoints.","Schema-validate the top-level trust_domains key before parsing.","Keep bundle and bundle-map files in separate directories to avoid mixing them."],"tags":["grpc","spiffe","mtls","trust-bundle","json-parsing"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}