{"record":{"id":"a54e16adf9870aa1","repo":"gastownhall/beads","slug":"identity-invalid-reply-mac","errorCode":null,"errorMessage":"identity: invalid reply MAC","messagePattern":"identity: invalid reply MAC","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dbproxy/identity/control.go","lineNumber":105,"sourceCode":"\tif err != nil {\n\t\treturn IdentReply{}, err\n\t}\n\tpayload, err := canonicalIdentReply(reply)\n\tif err != nil {\n\t\treturn IdentReply{}, err\n\t}\n\tmac := hmac.New(sha256.New, []byte(secret))\n\t_, _ = mac.Write(nonceBytes)\n\t_, _ = mac.Write(payload)\n\treply.MAC = hex.EncodeToString(mac.Sum(nil))\n\treturn reply, nil\n}\n\n// VerifyIdentReply verifies the authenticated reply for the request nonce.\nfunc VerifyIdentReply(reply IdentReply, secret, nonce string) error {\n\tgot, err := hex.DecodeString(reply.MAC)\n\tif err != nil {\n\t\treturn errors.New(\"identity: invalid reply MAC\")\n\t}\n\tsigned, err := SignIdentReply(reply, secret, nonce)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"identity: authenticate reply: %w\", err)\n\t}\n\twant, err := hex.DecodeString(signed.MAC)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"identity: decode expected reply MAC: %w\", err)\n\t}\n\tif !hmac.Equal(got, want) {\n\t\treturn errors.New(\"identity: reply authentication failed\")\n\t}\n\treturn nil\n}\n\nfunc decodeIdentNonce(nonce string) ([]byte, error) {\n\traw, err := hex.DecodeString(nonce)\n\tif err != nil || len(raw) != identNonceBytes {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dbproxy/identity/control.go#L87-L123","documentation":"VerifyIdentReply authenticates an IdentReply by comparing its MAC against a freshly computed HMAC over the canonical reply, the shared secret, and the request nonce. This error means the reply.MAC field is not valid hex, so it cannot even be decoded for comparison — the peer is not producing protocol-conformant replies.","triggerScenarios":"Calling VerifyIdentReply (directly or via Identify) on a reply whose MAC field contains non-hex characters — empty MAC, base64-encoded MAC, or a tampered/garbage reply from an unauthenticated peer.","commonSituations":"Rogue process on the control socket replying with arbitrary JSON that happens to decode into IdentReply; protocol version mismatch changing MAC encoding; tests feeding hand-crafted replies.","solutions":["Ensure the peer runs a compatible proxy version producing hex-encoded HMAC-SHA256 MACs","Treat as an unauthenticated peer: do not trust any reply fields and disconnect","Verify both sides derive the same 64-char hex secret (see ReadSecret) — encoding mismatches often start at the secret","Regenerate the proxy secret and re-run discovery if corruption is suspected"],"exampleFix":"// before\nif err := identity.VerifyIdentReply(reply, secret, nonce); err != nil { return err }\n// after\nif err := identity.VerifyIdentReply(reply, secret, nonce); err != nil {\n    if strings.Contains(err.Error(), \"invalid reply MAC\") { return ErrUntrustedPeer } // reject peer entirely\n    return err\n}","handlingStrategy":"type-guard","validationCode":"if _, err := hex.DecodeString(reply.MAC); err != nil { return ErrUntrustedPeer }","typeGuard":"func isInvalidMAC(err error) bool { return strings.Contains(err.Error(), \"invalid reply MAC\") }","tryCatchPattern":"if err := identity.VerifyIdentReply(reply, secret, nonce); err != nil {\n    if isInvalidMAC(err) { return ErrUntrustedPeer } // reject, do not trust any fields\n    return err\n}","preventionTips":["Run compatible proxy versions that emit hex-encoded HMACs","Never trust reply fields when MAC verification fails","Keep secrets hex-encoded on both sides to avoid encoding drift","Regenerate secrets if corruption is suspected"],"tags":["identity","hmac","security","protocol-violation"],"backgroundTag":"invalid-signature","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}