Skip to content

fix(message): decrypt secretEncryptedMessage MESSAGE_EDIT envelopes - #153

Open
Caio-HD wants to merge 2 commits into
evolution-foundation:mainfrom
Caio-HD:fix/decrypt-secret-encrypted-message-edit
Open

fix(message): decrypt secretEncryptedMessage MESSAGE_EDIT envelopes#153
Caio-HD wants to merge 2 commits into
evolution-foundation:mainfrom
Caio-HD:fix/decrypt-secret-encrypted-message-edit

Conversation

@Caio-HD

@Caio-HD Caio-HD commented Aug 3, 2026

Copy link
Copy Markdown

Description

WhatsApp no longer sends message edits as a plaintext protocolMessage. The edited content now
arrives sealed in a secretEncryptedMessage envelope with secretEncType: MESSAGE_EDIT, encrypted
with a key derived from the target message secret.

evolution-go forwards that envelope untouched, so utils.GetMessageType classifies it as
secret encrypted and the webhook carries no text at all. Consumers can tell that something was
edited only by parsing the envelope themselves — and they can't read what it says, because the
payload is encrypted.

whatsmeow already solves the decryption: Client.DecryptSecretEncryptedMessage handles the
MESSAGE_EDIT case, and the whatsmeow version pinned in go.mod
(v0.0.0-20260630180629-b572e5bcb92b) includes it. This PR just wires it up.

In the *events.Message handler the envelope is now decrypted and the message rewritten in place
to the plaintext shape consumers already handle:

protocolMessage{ type: MESSAGE_EDIT, key: <target message key>, editedMessage: <new content> }

Since that is the exact shape WhatsApp used before, no consumer contract changes:
GetMessageType returns edit just as it did for plaintext edits, and the webhook payload now
carries the corrected text.

Failure behaviour

Every failure path leaves the event exactly as it behaves today — the envelope is forwarded, never
dropped:

  • decryption fails (e.g. ErrOriginalMessageSecretNotFound) → logged, envelope forwarded;
  • no client for the instance → envelope forwarded;
  • envelope without a targetMessageKey → logged, envelope forwarded.

Known limitation

whatsmeow derives the key from the original message secret (Store.MsgSecrets), so an edit of a
message the instance never received cannot be decrypted. That is still strictly better than the
current behaviour, where no edit can be read at all.

Related Issue

N/A — reported downstream: edits reach consumers as an empty/opaque message while the original text
stays stale.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement

Testing

Automated:

  • go test ./pkg/whatsmeow/service/... — passing, including 5 new tests in secret_edit_test.go
    covering envelope detection (MESSAGE_EDIT vs POLL_EDIT vs plain message), the rebuilt
    protocolMessage shape, the timestamp being carried over and omitted when unknown, the nil
    guards, and the end result being typed as edit by utils.GetMessageType.
  • go vet ./pkg/whatsmeow/service/... — clean.
  • gofmt — clean.
  • Manual testing completed
  • Functionality verified in development environment
  • No breaking changes introduced

Additional Notes

The change is deliberately scoped to MESSAGE_EDIT. Other secretEncType values (EVENT_EDIT,
POLL_EDIT, POLL_ADD_OPTION) keep their current behaviour, since each one needs its own target
shape and they are not part of this fix.

Summary by Sourcery

Handle WhatsApp message edits sent in secretEncryptedMessage envelopes by decrypting and rewriting them into the existing plaintext edit protocolMessage shape before message typing and webhook delivery.

Bug Fixes:

  • Decrypt MESSAGE_EDIT secretEncryptedMessage envelopes and rewrite them into plaintext edit protocol messages so webhooks carry the updated text instead of an opaque blob.

Enhancements:

  • Introduce helper flow to detect secretEncrypted MESSAGE_EDIT envelopes, decrypt them using the client, and rebuild standard edit protocol messages without changing downstream contracts.

Tests:

  • Add unit tests covering detection of secretEncrypted edit envelopes, rebuilding of plaintext protocolMessage edits (including timestamp handling and nil guards), and verification that rebuilt messages are classified as edits by GetMessageType.

WhatsApp now seals message edits in a secretEncryptedMessage envelope
instead of a plaintext protocolMessage, so edits reached consumers as an
opaque blob with no readable text.

Decrypt it with whatsmeow's DecryptSecretEncryptedMessage and rewrite the
message in place to the protocolMessage{MESSAGE_EDIT} shape consumers
already handle. Every failure path forwards the envelope untouched, so a
message is never dropped.
@sourcery-ai

sourcery-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Reviewer's Guide

Decrypts WhatsApp MESSAGE_EDIT secretEncryptedMessage envelopes and rewrites them into the legacy plaintext protocolMessage shape so downstream message typing and webhooks see the edited text, with conservative failure handling and targeted tests for the new behavior.

Sequence diagram for decrypting MESSAGE_EDIT secretEncryptedMessage envelopes

sequenceDiagram
    participant MyClient
    participant MyClientClient as Client
    participant EventsMessage as events.Message

    MyClient->>EventsMessage: myEventHandler(rawEvt)
    activate EventsMessage
    MyClient->>EventsMessage: unwrapSecretEncryptedEdit(evt)
    alt evt is MESSAGE_EDIT secretEncryptedMessage
        MyClient->>MyClientClient: DecryptSecretEncryptedMessage(context, evt)
        MyClientClient-->>MyClient: decryptedMessage or error
        alt decryptedMessage and targetMessageKey present
            MyClient->>MyClient: buildEditProtocolMessage(targetKey, decryptedMessage, timestampMS)
            MyClient-->>EventsMessage: evt.Message = rebuiltProtocolMessage
        else decryption failed or missing targetMessageKey
            MyClient-->>EventsMessage: evt.Message unchanged (envelope forwarded)
        end
    else not MESSAGE_EDIT secretEncryptedMessage
        MyClient-->>EventsMessage: evt.Message unchanged
    end
    MyClient->>MyClient: utils.GetMessageType(evt.Message)
    deactivate EventsMessage
Loading

File-Level Changes

Change Details Files
Decrypt MESSAGE_EDIT secretEncryptedMessage envelopes in the message event handler and rewrite them into a plaintext protocolMessage edit before type classification and webhook emission.
  • Hooked unwrapSecretEncryptedEdit into MyClient.myEventHandler before GetMessageType is invoked so edits are classified correctly.
  • Added logic to detect MESSAGE_EDIT secretEncryptedMessage envelopes and skip non-edit or nil messages.
  • Used the existing whatsmeow client for the instance to decrypt the secretEncryptedMessage payload, logging and returning on failure.
  • Rebuilt the decrypted content into a ProtocolMessage MESSAGE_EDIT with target key, editedMessage, and optional timestamp, and replaced evt.Message with the rebuilt message.
  • Preserved current behavior when decryption fails, client is missing, or rebuild preconditions are not met, forwarding the original envelope untouched.
pkg/whatsmeow/service/whatsmeow.go
pkg/whatsmeow/service/secret_edit.go
Add focused unit tests to validate edit envelope detection, protocolMessage rebuilding semantics, timestamp handling, nil guards, and final message typing as edit.
  • Created helper for constructing SecretEncryptedMessage envelopes with different SecretEncType values for tests.
  • Verified isSecretEncryptedEdit behavior for MESSAGE_EDIT, POLL_EDIT, plain messages, and nil messages.
  • Tested that buildEditProtocolMessage produces the expected MESSAGE_EDIT ProtocolMessage shape with preserved target key, decrypted text, and timestamp when provided.
  • Ensured buildEditProtocolMessage omits the timestamp when the event carries none and returns nil when target or decrypted content is missing.
  • Confirmed that utils.GetMessageType classifies sealed envelopes as secret encrypted and rebuilt messages as edit.
pkg/whatsmeow/service/secret_edit_test.go

Possibly linked issues

  • #408: PR decrypts MESSAGE_EDIT envelopes and rebuilds edit protocolMessage, restoring updated text and messages.update behavior.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • isSecretEncryptedEdit dereferences message without a nil check but the tests expect it to handle nil safely; add an explicit message == nil guard to avoid panics.
  • In unwrapSecretEncryptedEdit you call GetSecretEncryptedMessage twice; consider storing the result in a local variable to avoid repeated lookups and make the intent clearer when accessing TargetMessageKey.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- isSecretEncryptedEdit dereferences message without a nil check but the tests expect it to handle nil safely; add an explicit message == nil guard to avoid panics.
- In unwrapSecretEncryptedEdit you call GetSecretEncryptedMessage twice; consider storing the result in a local variable to avoid repeated lookups and make the intent clearer when accessing TargetMessageKey.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Address review feedback: secretEncryptedEdit now returns the envelope it
matched, so the caller reaches TargetMessageKey from that value instead of
looking the envelope up a second time.

Also guard the nil message explicitly, matching the style already used by
getContextInfoFromMessage in referral.go.
@Caio-HD

Caio-HD commented Aug 3, 2026

Copy link
Copy Markdown
Author

Both addressed. secretEncryptedEdit now returns the matched envelope, so the caller reads TargetMessageKey from that value instead of looking it up again.

On the nil guard: there was no panic risk, GetSecretEncryptedMessage is a generated getter with a nil-receiver check, and the nil case was already covered by a passing test. Added the explicit guard anyway for consistency with getContextInfoFromMessage in referral.go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant