{"record":{"id":"4df0f730d37bbba4","repo":"slackhq/nebula","slug":"encoded-details-was-nil","errorCode":null,"errorMessage":"encoded Details was nil","messagePattern":"encoded Details was nil","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cert/cert_v1.go","lineNumber":413,"sourceCode":"\t}\n\tc.signature = b\n\treturn nil\n}\n\n// unmarshalCertificateV1 will unmarshal a protobuf byte representation of a nebula cert\n// if the publicKey is provided here then it is not required to be present in `b`\nfunc unmarshalCertificateV1(b []byte, publicKey []byte) (*certificateV1, error) {\n\tif len(b) == 0 {\n\t\treturn nil, fmt.Errorf(\"nil byte array\")\n\t}\n\tvar rc RawNebulaCertificate\n\terr := proto.Unmarshal(b, &rc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif rc.Details == nil {\n\t\treturn nil, fmt.Errorf(\"encoded Details was nil\")\n\t}\n\n\tif len(rc.Details.Ips)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded IPs should be in pairs, an odd number was found\")\n\t}\n\n\tif len(rc.Details.Subnets)%2 != 0 {\n\t\treturn nil, fmt.Errorf(\"encoded Subnets should be in pairs, an odd number was found\")\n\t}\n\n\tnc := certificateV1{\n\t\tdetails: detailsV1{\n\t\t\tname:           rc.Details.Name,\n\t\t\tgroups:         make([]string, len(rc.Details.Groups)),\n\t\t\tnetworks:       make([]netip.Prefix, len(rc.Details.Ips)/2),\n\t\t\tunsafeNetworks: make([]netip.Prefix, len(rc.Details.Subnets)/2),\n\t\t\tnotBefore:      time.Unix(rc.Details.NotBefore, 0),\n\t\t\tnotAfter:       time.Unix(rc.Details.NotAfter, 0),","sourceCodeStart":395,"sourceCodeEnd":431,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/cert/cert_v1.go#L395-L431","documentation":"After protobuf unmarshaling, the RawNebulaCertificate must contain a Details message; a nil Details means the serialized certificate lacks the required details payload (name, networks, ips, etc.), which is invalid for a v1 nebula certificate.","triggerScenarios":"unmarshalCertificateV1 receives bytes that decode to a RawNebulaCertificate with rc.Details == nil — e.g. an empty or hand-crafted protobuf message, or data corrupted so the Details submessage was dropped.","commonSituations":"Manually constructing or mutating RawNebulaCertificate without populating Details; truncation/corruption in transit; writing a certificate with only the version field set; test fixtures with minimal protobufs.","solutions":["Ensure the certificate is marshaled with Details populated (marshal via the library's Marshal, not by hand)","Validate the certificate source; re-read or re-issue the certificate","Check for corruption in transport/storage of the certificate bytes","Inspect the raw protobuf (e.g. protoc --decode) to confirm Details is present"],"exampleFix":"// before\nbad := &cert.RawNebulaCertificate{Version: 1}\nb, _ := proto.Marshal(bad) // Details nil\nunmarshalCertificateV1(b, nil)\n// after\ngood, _ := someCert.Marshal() // includes Details\nunmarshalCertificateV1(good, nil)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"c, err := unmarshalCertificateV1(b, nil)\nif err != nil {\n    if strings.Contains(err.Error(), \"encoded Details was nil\") {\n        return nil, fmt.Errorf(\"certificate bytes are malformed (missing Details); re-issue or re-read the cert\")\n    }\n    return nil, err\n}","preventionTips":["Always marshal certificates with the library's Marshal, never by hand-assembling protobufs","Validate certificate data integrity (hashes/signatures) in transit and at rest","Never mutate RawNebulaCertificate fields directly after unmarshal","Re-issue certificates that fail to round-trip marshal/unmarshal"],"tags":["certificate","protobuf","unmarshal","missing-field"],"backgroundTag":"malformed-certificate","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}