From 22e9c12f4947bdce077058a909887fa08c013bdf Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Feb 2026 00:45:47 +0000 Subject: [PATCH] improve: add detection and enhanced logging for corrupted Signal sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds comprehensive handling for Signal Protocol decryption errors including Bad MAC and MessageCounterError which indicate corrupted/desynchronized sessions. Changes: - Add BAD_MAC_ERROR_TEXT constant for error detection - Extend DECRYPTION_RETRY_CONFIG with corruptedSessionErrors array - Add NACK_REASONS.CorruptedSession (553) for protocol-level reporting - Add isCorruptedSessionError() utility function - Enhanced error logging to differentiate corrupted sessions from other errors - Include decryptionJid in error context for easier debugging Impact: - Better visibility into session corruption issues - Clearer logs with ⚠️ warning emoji for corrupted sessions - Foundation for future automatic session cleanup on corruption - Helps diagnose and resolve "Bad MAC" errors in production Related to PR #135 (session cleanup) - provides detection layer that complements the preventive session cleanup already implemented. https://claude.ai/code/session_01SoNUGBEWbJwWWws3F2fuzh --- src/Utils/decode-wa-message.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Utils/decode-wa-message.ts b/src/Utils/decode-wa-message.ts index 5a3b61c9..19531b60 100644 --- a/src/Utils/decode-wa-message.ts +++ b/src/Utils/decode-wa-message.ts @@ -51,12 +51,14 @@ const storeMappingFromEnvelope = async ( export const NO_MESSAGE_FOUND_ERROR_TEXT = 'Message absent from node' export const MISSING_KEYS_ERROR_TEXT = 'Key used already or never filled' +export const BAD_MAC_ERROR_TEXT = 'Bad MAC' // Retry configuration for failed decryption export const DECRYPTION_RETRY_CONFIG = { maxRetries: 3, baseDelayMs: 100, - sessionRecordErrors: ['No session record', 'SessionError: No session record'] + sessionRecordErrors: ['No session record', 'SessionError: No session record'], + corruptedSessionErrors: ['Bad MAC', 'MessageCounterError', MISSING_KEYS_ERROR_TEXT] } export const NACK_REASONS = { @@ -72,7 +74,8 @@ export const NACK_REASONS = { UnhandledError: 500, UnsupportedAdminRevoke: 550, UnsupportedLIDGroup: 551, - DBOperationFailed: 552 + DBOperationFailed: 552, + CorruptedSession: 553 } type MessageType = @@ -326,16 +329,28 @@ export const decryptMessageNode = ( fullMessage.message = msg } } catch (err: any) { + const isCorrupted = isCorruptedSessionError(err) + const isSessionRecord = isSessionRecordError(err) + const errorContext = { key: fullMessage.key, err, messageType: tag === 'plaintext' ? 'plaintext' : attrs.type, sender, author, - isSessionRecordError: isSessionRecordError(err) + decryptionJid, + isSessionRecordError: isSessionRecord, + isCorruptedSession: isCorrupted } - logger.error(errorContext, 'failed to decrypt message') + if (isCorrupted) { + logger.error( + errorContext, + '⚠️ Corrupted session detected - Bad MAC or MessageCounter error. Session may need to be recreated.' + ) + } else { + logger.error(errorContext, 'failed to decrypt message') + } fullMessage.messageStubType = proto.WebMessageInfo.StubType.CIPHERTEXT fullMessage.messageStubParameters = [err.message.toString()] @@ -359,3 +374,12 @@ function isSessionRecordError(error: any): boolean { const errorMessage = error?.message || error?.toString() || '' return DECRYPTION_RETRY_CONFIG.sessionRecordErrors.some(errorPattern => errorMessage.includes(errorPattern)) } + +/** + * Utility function to check if an error indicates a corrupted session + * (Bad MAC, MessageCounterError, Key already used) + */ +export function isCorruptedSessionError(error: any): boolean { + const errorMessage = error?.message || error?.toString() || '' + return DECRYPTION_RETRY_CONFIG.corruptedSessionErrors.some(errorPattern => errorMessage.includes(errorPattern)) +}