{"record":{"id":"1dfbab09f97a65ee","repo":"canopy-network/canopy","slug":"invalid-tag-at-offset-d","errorCode":null,"errorMessage":"invalid tag at offset %d","messagePattern":"invalid tag at offset (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/codec/codec.go","lineNumber":72,"sourceCode":"// ToAny() packs a protobuf message to a generic any\nfunc (p *Protobuf) ToAny(message proto.Message) (*anypb.Any, error) {\n\treturn anypb.New(message)\n}\n\n// FromAny() converts a proto any to the protobuf message\nfunc (p *Protobuf) FromAny(any *anypb.Any) (proto.Message, error) {\n\treturn anypb.UnmarshalNew(any, proto.UnmarshalOptions{})\n}\n\n// GetRawProtoField extracts the raw bytes for field number from a proto message\nfunc GetRawProtoField(protoBytes []byte, fieldNumber int) ([]byte, error) {\n\tvar offset int\n\t// parse the proto bytes to find a field\n\tfor offset < len(protoBytes) {\n\t\t// decode the field tag (field number + wire type)\n\t\tfieldNum, wireType, tagLen := protowire.ConsumeTag(protoBytes[offset:])\n\t\tif tagLen < 0 {\n\t\t\treturn nil, fmt.Errorf(\"invalid tag at offset %d\", offset)\n\t\t}\n\t\toffset += tagLen\n\t\t// check if this is the field we're looking for\n\t\tif int(fieldNum) == fieldNumber {\n\t\t\t// for length-delimited fields (like messages), we need to read the length\n\t\t\tif wireType == protowire.BytesType {\n\t\t\t\t// read the length of the field value\n\t\t\t\tvalueLen, lenBytes := protowire.ConsumeVarint(protoBytes[offset:])\n\t\t\t\tif lenBytes < 0 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid length at offset %d\", offset)\n\t\t\t\t}\n\t\t\t\t// calculate the new offset\n\t\t\t\toffset += lenBytes\n\t\t\t\t// extract the field value bytes\n\t\t\t\tif offset+int(valueLen) > len(protoBytes) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"field value exceeds buffer bounds\")\n\t\t\t\t}\n\t\t\t\t// make buffer to return","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/canopy-network/canopy/blob/ee8197d91dd410f6592cb650a94c925ee6dc8bad/lib/codec/codec.go#L54-L90","documentation":"GetRawProtoField manually walks protobuf wire format. At each offset it decodes a field tag with protowire.ConsumeTag; a negative tag length means the bytes at that offset are not a valid varint tag — the buffer is corrupt, truncated, or not protobuf wire data at all.","triggerScenarios":"Calling GetRawProtoField (directly or via entryKeyOrZero / BytesToBlockHash) with bytes that are not valid proto wire format, or a truncated slice where the loop lands mid-tag.","commonSituations":"Passing JSON, hex/base64-decoded-but-wrong data, or a doubly/mis-encoded payload instead of proto.Marshal output; slicing the proto bytes at the wrong offset before calling; corrupted storage reads.","solutions":["Verify the bytes come from proto.Marshal (or codec.Marshal) of the expected message — log/inspect the first few bytes.","If the data may be JSON or another format, branch on format before calling GetRawProtoField and decode accordingly.","Round-trip sanity check: proto.Unmarshal(bytes, &msg{}) should succeed before doing raw wire scanning."],"exampleFix":"// before\nraw, _ := codec.GetRawProtoField(jsonBytes, 1)\n// after\nprotoBytes, err := codec.Protobuf{}.Marshal(msg)\nif err != nil { return err }\nraw, err := codec.GetRawProtoField(protoBytes, 1)","handlingStrategy":"type-guard","validationCode":"if len(data) == 0 {\n    return fmt.Errorf(\"empty proto bytes\")\n}\nvar probe pb.Event\nif err := proto.Unmarshal(data, &probe); err != nil {\n    return fmt.Errorf(\"not valid protobuf wire data: %w\", err)\n}","typeGuard":"func isProtoWireData(data []byte) bool {\n    var m ptypes.DynamicAny\n    return proto.Unmarshal(data, &m.Message) == nil || protobufUnmarshalOK(data)\n}","tryCatchPattern":"raw, err := codec.GetRawProtoField(data, fieldNum)\nif err != nil && strings.Contains(err.Error(), \"invalid tag\") {\n    return fmt.Errorf(\"corrupt or non-proto payload: %w\", err)\n}","preventionTips":["Only feed GetRawProtoField bytes produced by proto.Marshal.","Tag stored blobs with a format marker so JSON/proto are never confused.","Round-trip test (Marshal then GetRawProtoField) in unit tests."],"tags":["protobuf","wire-format","corrupt-data","parsing"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"ee8197d91dd410f6592cb650a94c925ee6dc8bad","analyzedAt":"2026-09-06T09:30:15.973Z","contentChangedAt":"2026-09-06T09:30:15.973Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}