Skip to content

feat: decrypt secret-encrypted message edits#128

Open
FlavioPulli wants to merge 1 commit into
evolution-foundation:mainfrom
FlavioPulli:fix/secret-message-edit-decrypt
Open

feat: decrypt secret-encrypted message edits#128
FlavioPulli wants to merge 1 commit into
evolution-foundation:mainfrom
FlavioPulli:fix/secret-message-edit-decrypt

Conversation

@FlavioPulli

@FlavioPulli FlavioPulli commented Jul 24, 2026

Copy link
Copy Markdown

When a phone edits a message in a @lid-addressed chat, WhatsApp delivers the edit as secretEncryptedMessage (MESSAGE_EDIT): the new content is encrypted with the message secret of the original message — a secret the whatsmeow session already holds (it arrived inside the original message). Today the encrypted event is forwarded to the webhook as-is, so consumers learn that an edit happened but not what changed.

This decrypts it with whatsmeow's official primitive (Client.DecryptSecretEncryptedMessage, native EncSecretMessageEdit support) and rewrites the event into the canonical cleartext shape (protocolMessage MESSAGE_EDIT + editedMessage) — the same shape BuildEdit produces — so webhook consumers handle phone edits and app edits identically.

Fail-open by design: if the secret is missing (message predates the pairing) the event passes through unchanged. Self-contained file plus a one-line call-site to keep review/rebase easy. Battle-tested in our production since 2026-07-21.

Summary by Sourcery

Decrypt secret-encrypted message edit events and normalize them into the standard cleartext edit shape before forwarding to webhooks.

New Features:

  • Support decryption of secretEncryptedMessage MESSAGE_EDIT events using the existing whatsmeow client message secrets.
  • Expose phone-originated edits to webhook consumers in the same cleartext format as application-originated edits.

Enhancements:

  • Introduce a dedicated helper to resolve secret-encrypted edits while preserving fail-open behavior when decryption is not possible.

When a phone edits a message in a @lid-addressed chat, WhatsApp delivers
the edit as secretEncryptedMessage (MESSAGE_EDIT), encrypted with the
original message's "message secret" — which this whatsmeow session
already holds. Today the encrypted event is forwarded to the webhook
as-is, so consumers learn that an edit happened but not what changed.

Decrypt it with whatsmeow's official primitive
(Client.DecryptSecretEncryptedMessage, native EncSecretMessageEdit
support) and rewrite the event into the canonical cleartext shape
(protocolMessage MESSAGE_EDIT + editedMessage) — the same shape
BuildEdit produces — so webhook consumers handle phone edits and app
edits identically. Fail-open: if the secret is missing (message predates
the pairing) the event passes through unchanged.

Battle-tested in production since 2026-07-21 against real phone edits.
@sourcery-ai

sourcery-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Decrypts secret-encrypted message edits from phones and rewrites them into the canonical cleartext MESSAGE_EDIT shape before forwarding to webhooks, while failing open when decryption is not possible.

Sequence diagram for decrypting secret-encrypted MESSAGE_EDIT before webhook forwarding

sequenceDiagram
  actor Phone
  participant WhatsAppServer
  participant MyClient
  participant WAClient
  participant WebhookConsumer

  Phone->>WhatsAppServer: secretEncryptedMessage MESSAGE_EDIT
  WhatsAppServer->>MyClient: events.Message (secretEncryptedMessage)
  MyClient->>MyClient: myEventHandler(rawEvt)
  MyClient->>MyClient: resolveSecretEncryptedEdit(evt)
  MyClient->>WAClient: DecryptSecretEncryptedMessage(ctx, evt)
  alt [decryption succeeds]
    WAClient-->>MyClient: decrypted waE2E.Message
    MyClient->>MyClient: rewrite evt.Message to protocolMessage MESSAGE_EDIT + editedMessage
  else [decryption fails or unsupported]
    WAClient-->>MyClient: error
    MyClient->>MyClient: leave evt.Message as secretEncryptedMessage
  end
  MyClient->>WebhookConsumer: forward Message event (cleartext when available)
Loading

File-Level Changes

Change Details Files
Hook message handler into a resolver that converts secret-encrypted message edits into cleartext protocolMessage edits when possible.
  • Invoke a new helper on each incoming message event before existing logging and processing.
  • Ensure secret-encrypted edits are normalized so downstream webhook consumers see a standard MESSAGE_EDIT with editedMessage content.
pkg/whatsmeow/service/whatsmeow.go
Add helper to detect, decrypt, and rewrite secret-encrypted MESSAGE_EDIT events into the canonical cleartext structure, with fail-open behavior and logging.
  • Inspect incoming events for secretEncryptedMessage payloads of type MESSAGE_EDIT and return early when not present.
  • Use Client.DecryptSecretEncryptedMessage to obtain the decrypted message content tied to the original message secret.
  • On decryption failure, log a warning and leave the event untouched so webhooks still receive the encrypted payload and target message key.
  • When the decrypted payload is already a protocolMessage MESSAGE_EDIT with editedMessage, replace evt.Message with it directly.
  • Otherwise, construct a new waE2E.Message containing a protocolMessage of type MESSAGE_EDIT, wired to the targetMessageKey, embedding the decrypted content as editedMessage, and stamping it with the event timestamp.
  • Log successful decryptions at info level to aid observability and debugging.
pkg/whatsmeow/service/secret_edit.go

Possibly linked issues

  • #: PR decrypts secretEncryptedMessage MESSAGE_EDIT into protocolMessage+editedMessage, providing clear edited text as requested.
  • #: PR decrypts secretEncryptedMessage edits and rewrites them into canonical MESSAGE_EDIT with editedMessage, exposing new text.

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 found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pkg/whatsmeow/service/secret_edit.go" line_range="34-35" />
<code_context>
+// failure (missing secret, unsupported type, …) the event passes through
+// untouched — behavior falls back to what it was before this change (webhook
+// receives the encrypted payload).
+func (mycli *MyClient) resolveSecretEncryptedEdit(evt *events.Message) {
+	enc := evt.Message.GetSecretEncryptedMessage()
+	if enc == nil || enc.GetSecretEncType() != waE2E.SecretEncryptedMessage_MESSAGE_EDIT {
+		return
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against a nil targetMessageKey before logging its ID

Both log lines call enc.GetTargetMessageKey().GetID() without confirming targetMessageKey is non-nil. If a payload is missing targetMessageKey, this will panic while attempting to log. Please add a nil check and either skip logging the ID or use a safe placeholder to avoid crashes on malformed inputs.
</issue_to_address>

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.

Comment on lines +34 to +35
func (mycli *MyClient) resolveSecretEncryptedEdit(evt *events.Message) {
enc := evt.Message.GetSecretEncryptedMessage()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Guard against a nil targetMessageKey before logging its ID

Both log lines call enc.GetTargetMessageKey().GetID() without confirming targetMessageKey is non-nil. If a payload is missing targetMessageKey, this will panic while attempting to log. Please add a nil check and either skip logging the ID or use a safe placeholder to avoid crashes on malformed inputs.

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