Skip to content

feat(message_edit): decrypt secretEncryptedMessage MESSAGE_EDIT envelope - #618

Merged
jlucaso1 merged 4 commits into
mainfrom
feat/secret-encrypted-message-edit
May 11, 2026
Merged

feat(message_edit): decrypt secretEncryptedMessage MESSAGE_EDIT envelope#618
jlucaso1 merged 4 commits into
mainfrom
feat/secret-encrypted-message-edit

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

WhatsApp wraps message edits in an E2E envelope (secret_encrypted_message with secret_enc_type = MESSAGE_EDIT) keyed by the original message's messageContextInfo.messageSecret. Clients that only listened to the legacy protocolMessage.editedMessage path were missing edits from newer senders. The proto already had SecretEncryptedMessage / MessageEdit = 2 but no receiver code consumed the field.

This PR adds the receiver primitives, verified against a recent WA Web bundle. It does not auto-decrypt on the dispatch path (see "Out of scope" below) — consumers call the new helper from their Event::Message handler, same pattern as Polls::decrypt_vote.

Algorithm

```
info = msgId || origSenderJid || editorJid || "Message Edit"
salt = zeros[32]
key = HKDF-SHA256(salt, ikm = messageSecret, info, L = 32)
aad = (empty)
ct = AES-256-GCM-Encrypt(key, iv12, aad, plaintext)
```

Plaintext is a `Message` proto whose `protocolMessage.editedMessage` carries the new content.

Evidence (WA Web modules)

Claim Module
Use-case literal `"Message Edit"` and HKDF info ordering (`stanzaId ‖ parentOrigSender ‖ editor ‖ usecase`) `WAUseCaseSecret.createUseCaseSecret`
Envelope detection + 12-byte IV check `WAWebParseMessageEditEncryptedMessageProto`
Invocation contract (`decryptAddOn` inputs) `WAWebProcessEncryptedMessageEditMsgs`
AAD empty for everything except PollVote / EventResponse `WAWebAddonEncryption` function `g`

The Baileys PR #2547 that prompted this work claims the same algorithm; each constant was verified independently against the captured WA Web bundle rather than trusting the PR.

Changes

  • `wacore::secret_enc_addon` (new): generic HKDF + AES-GCM helper covering the full addon family — PollVote, PollEdit, PollAddOption, EventResponse, EventEdit, EncReaction, EncComment, ReportToken, MessageEdit. `ModificationType::aad_mode()` encodes the rule from `WAWebAddonEncryption` function `g` so the AAD-empty-for-edits invariant is locked at the type level.
  • `wacore::message_edit` (new): typed encrypt/decrypt for the MESSAGE_EDIT envelope, plus a 2-attempt LID/PN fallback mirroring `decryptAddOn`.
  • `MessageEdits` feature (new, `Client::message_edits()`): JID normalisation via `to_non_ad()` (matches `widToUserJid`), envelope extraction with `SecretEncType` type-check + IV validation, and `rewrap_as_legacy_edit` that converts the decrypted inner message into the legacy `protocolMessage.editedMessage` shape so consumers handle one shape regardless of envelope.
  • `wacore::poll` (refactor): delegates to the generic helper. Public API preserved; new `_with_secret` single-step variants are the recommended path (used by `Polls` internally — one alloc less per vote).

Out of scope (intentional)

  • Auto-decrypt on the dispatch path. Doing it cleanly needs a message-getter callback so the library can look up the parent's `messageSecret`; that's a sizable new API surface. Leaving it for a follow-up — current consumer pattern matches polls.
  • POLL_EDIT, EVENT_EDIT, POLL_ADD_OPTION receivers. `secret_enc_addon` already covers their constants; the high-level helpers are a copy of `message_edit.rs` with a different `ModificationType` and inner-proto decode. Trivial follow-up.
  • Reporting-token / dual-encrypted validation that `WAWebHandleMsgValidate.validateAndProcessReportingTokenInfo` runs on the WA Web side. Consistent with the rest of the library.

Test plan

  • `cargo fmt --all`
  • `cargo clippy --all --tests --exclude e2e-tests -- -D warnings` (zero warnings)
  • `cargo test --workspace --exclude e2e-tests --lib` — 1404 tests pass, 0 fail
  • New tests:
    • `secret_enc_addon`: literals match WA Web, AAD-mode matrix matches `WAWebAddonEncryption` function `g`, key derivation changes with each input, encrypt/decrypt round-trip for MessageEdit and PollVote, wrong-AAD detection, IV-size + payload-size validation. 9 tests.
    • `message_edit`: text round-trip, LID-JID round-trip, wrong editor JID fails GCM, wrong secret fails GCM, invalid IV length rejected, fallback rescues on alternate JID form, fallback returns combined error when both fail. 7 tests.
    • `MessageEdits` feature: device-suffix normalisation, envelope detection, rejection of non-MessageEdit `SecretEncType`, rejection of invalid IV size, legacy-shape rewrap, none-when-inner-missing. 6 tests.
  • Live test against a recent WA Android client edit — pending a test environment.

WhatsApp wraps message edits in an E2E envelope
(`secret_encrypted_message` with `secret_enc_type = MESSAGE_EDIT`) keyed by
the original message's `messageContextInfo.messageSecret`. Clients that only
listened to the legacy `protocolMessage.editedMessage` path were missing
edits from newer senders.

Add the receiver primitives, verified against `docs/captured-js/`:

- `wacore::secret_enc_addon` — generic HKDF-SHA256 + AES-256-GCM helper
  covering the full addon family (PollVote, PollEdit, PollAddOption,
  EventResponse, EventEdit, EncReaction, EncComment, ReportToken,
  MessageEdit). `ModificationType::aad_mode()` encodes the rule from
  `WAWebAddonEncryption.js` function `g`: only `PollVote` and
  `EventResponse` bind `stanzaId\0sender` into AAD, everything else
  (including all edits) uses empty AAD.
- `wacore::message_edit` — typed encrypt/decrypt for the MESSAGE_EDIT
  envelope plus a 2-attempt LID/PN fallback mirroring `decryptAddOn`.
- `MessageEdits` feature surface (`Client::message_edits()`) — JID
  normalisation via `to_non_ad()`, envelope extraction, and a
  `rewrap_as_legacy_edit` helper that re-shapes the decrypted inner
  message into the legacy `protocolMessage.editedMessage` form so
  downstream consumers handle one shape regardless of envelope.

`wacore::poll` now delegates to the generic helper while keeping its
public API (a `_with_secret` variant is exposed for the recommended
single-step path).

Auto-decrypt on the dispatch path is intentionally not wired here —
that requires a message-getter callback to look up the parent's
`messageSecret`. Consumers call `MessageEdits::decrypt` from their
`Event::Message` handler, same pattern as `Polls::decrypt_vote`.
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added end-to-end encrypted message-edit support so edited messages can be securely transmitted and revealed to recipients.
  • Improvements

    • Unified and strengthened addon-style encryption primitives used by polls and other secret-encrypted payloads, improving key derivation and AAD handling for poll votes.
  • Tests

    • Added extensive unit tests covering message edit flows, poll vote encryption/decryption, and edge cases.

Walkthrough

Adds a shared secret-addon encryption framework (HKDF-SHA256 + AES-256-GCM with AAD modes), implements MESSAGE_EDIT envelope encryption/decryption with JID normalization and optional fallback, migrates poll vote crypto to the addon framework, and exposes high-level feature helpers and module wiring with unit tests.

Changes

Message Edit E2EE with Shared Addon Framework

Layer / File(s) Summary
Shared Addon Encryption Framework
wacore/src/secret_enc_addon.rs
Adds ModificationType/AadMode, AddonContext, derive_use_case_secret (HKDF-SHA256), build_aad, encrypt_addon, decrypt_addon, and tests for AAD modes, key sensitivity, roundtrips, tampering, and invalid IV/ciphertext cases.
Message Edit Crypto Core
wacore/src/message_edit.rs
Adds MessageEditContext, encrypt_message_edit, decrypt_message_edit (12-byte IV enforcement + protobuf decode), and decrypt_message_edit_with_fallback (primary + optional fallback) with unit tests for roundtrips and failure modes.
Poll Vote Crypto Migration
wacore/src/poll.rs
Refactors poll vote crypto to use secret_enc_addon: derive_vote_encryption_key delegates to derive_use_case_secret; adds encrypt_poll_vote_with_secret / decrypt_poll_vote_with_secret; updates tests and preserves legacy interfaces with updated AAD handling.
Message Edit Public API
src/features/message_edit.rs
Adds high-level APIs: decrypt, decrypt_with_fallback (JID normalization), extract_envelope (MESSAGE_EDIT detection, enc_payload/enc_iv/target_message_key extraction, IV-size validation), rewrap_as_legacy_edit, and EncryptedEdit helpers plus unit tests for parsing, IV validation, JID resolution, and legacy rewrap.
Feature Module Wiring
wacore/src/lib.rs, src/features/mod.rs, src/lib.rs
Exports new wacore submodules (message_edit, secret_enc_addon), registers message_edit feature module, re-exports EncryptedEdit, and reformats pub use features layout.
Poll Feature Usage
src/features/polls.rs
Replaces manual derive+encrypt/decrypt with encrypt_poll_vote_with_secret / decrypt_poll_vote_with_secret in vote(), decrypt_vote(), and aggregate_votes(), removing per-voter derivation branches.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Features as src::features
  participant wacore_message_edit as wacore::message_edit
  participant secret_addon as secret_enc_addon
  participant AESGCM as AES-256-GCM
  Client->>Features: extract_envelope(msg) -> EncryptedEdit
  Features->>wacore_message_edit: decrypt_message_edit_with_fallback(enc_payload, iv, message_secret, primary_ctx, fallback?)
  wacore_message_edit->>secret_addon: decrypt_addon(enc_payload, iv, message_secret, AddonContext)
  secret_addon->>AESGCM: decrypt with derived key + AAD, verify tag
  AESGCM-->>secret_addon: plaintext or error
  secret_addon-->>wacore_message_edit: plaintext or error
  wacore_message_edit->>Client: decoded inner waproto::Message or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main feature addition—decrypting MESSAGE_EDIT secret-encrypted envelopes—which is the primary objective across all the changeset modifications.
Description check ✅ Passed The description comprehensively explains the MESSAGE_EDIT envelope decryption feature, algorithm, evidence from WA Web, changes across multiple modules, and test coverage, all directly relevant to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/secret-encrypted-message-edit

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ee71285ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +191 to +196
let raw = self
.target_message_key
.participant
.as_deref()
.or(self.target_message_key.remote_jid.as_deref())
.ok_or_else(|| anyhow!("target message key missing participant and remote_jid"))?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve sender from from_me before falling back to remote_jid

When target_message_key.participant is absent, this helper always treats remote_jid as the original sender. In 1:1 chats, keys for messages sent by the local user have from_me=true and remote_jid set to the peer, so using remote_jid here derives the wrong HKDF context and causes MESSAGE_EDIT decryption to fail for edits of self-authored messages unless callers bypass this helper. This new API should branch on from_me (using the local account JID) instead of unconditionally using remote_jid.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wacore/src/poll.rs (1)

74-106: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Legacy decrypt_poll_vote duplicates build_aad logic — acceptable for now.

Lines 95-98 hand-roll the PollVote AAD (stanza_id || 0x00 || voter_jid), which is exactly what secret_enc_addon::build_aad does. Since decrypt_poll_vote takes a pre-derived key (not a message_secret), it can't just call decrypt_addon. If you want to kill this duplication without changing the signature, make secret_enc_addon::build_aad pub(crate) and call it here. Not blocking — but it's a real foot-gun if someone tweaks the AAD format in one place and forgets the other.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/poll.rs` around lines 74 - 106, The decrypt_poll_vote function
duplicates the AAD construction (stanza_id || 0x00 || voter_jid), which risks
divergence from secret_enc_addon::build_aad; make secret_enc_addon::build_aad
pub(crate) and replace the manual AAD assembly in decrypt_poll_vote with a call
to secret_enc_addon::build_aad(stanza_id, voter_jid) so the same canonical AAD
is used everywhere (do not change decrypt_poll_vote's signature or its use of
the pre-derived encryption_key; keep decrypt_addon and message_secret
untouched).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/message_edit.rs`:
- Around line 126-144: In extract_envelope, keep returning None for invalid IV
lengths but add a diagnostic log before the return: when enc_iv.len() != 12,
call log::warn! (including contextual identifiers such as that the message had
secret_encrypted_message, the secret_enc_type is MessageEdit, and any available
message id or target_message_key) so malformed envelopes are visible in logs;
ensure you import/use the log crate and avoid exposing sensitive payload
contents (do not log enc_payload).
- Around line 31-38: MessageEdits currently just wraps a Client but all public
routines (MessageEdits::decrypt, decrypt_with_fallback, extract_envelope,
rewrap_as_legacy_edit) are static/associated functions, which misleads callers;
either convert the API to actually use the stored client by making the public
methods take &self (e.g. implement decrypt_envelope(&self, ...) that calls
self._client where needed and composes extract_envelope + decrypt +
rewrap_as_legacy_edit), or remove the MessageEdits struct and export those
functions as plain pub fn in the module; pick one approach and update
Client::message_edits() and any call sites accordingly so the surface matches
the implementation intent.

In `@wacore/src/poll.rs`:
- Around line 41-68: The public function encrypt_poll_vote was removed, breaking
downstream callers; restore a public wrapper named pub fn encrypt_poll_vote with
the original signature and behavior, implement it to derive or accept the same
message_secret as before (mirror how decrypt_poll_vote expects inputs) and
delegate to encrypt_poll_vote_with_secret so behavior is identical, keep or
re-add original doc comment and visibility so the API surface matches
decrypt_poll_vote; if you want to signal transition, mark the wrapper deprecated
but keep it present to avoid the breaking change.

In `@wacore/src/secret_enc_addon.rs`:
- Around line 331-342: The test message_edit_aad_is_empty_unlike_poll_vote
currently only does a MessageEdit roundtrip and doesn't exercise AAD-branching;
either remove/update the misleading comment or change the test to derive the
encryption key once and then call the low-level AES-GCM helpers with two
different AAD buffers (MessageEdit vs PollVote) to ensure decryption fails when
AAD differs. To implement the latter, obtain the symmetric key the same way
encrypt_addon does (or expose build_aad/pub(crate) so you can call it) and then
call the AES-GCM encrypt/decrypt helpers directly using the two AADs (or use
aad_mode()/build_aad) to prove decryption fails due to AAD mismatch rather than
key derivation differences; update assertions accordingly. Ensure references to
ModificationType::MessageEdit and PollVote, encrypt_addon, decrypt_addon,
build_aad (or aad_mode) are used to locate the relevant code.

---

Outside diff comments:
In `@wacore/src/poll.rs`:
- Around line 74-106: The decrypt_poll_vote function duplicates the AAD
construction (stanza_id || 0x00 || voter_jid), which risks divergence from
secret_enc_addon::build_aad; make secret_enc_addon::build_aad pub(crate) and
replace the manual AAD assembly in decrypt_poll_vote with a call to
secret_enc_addon::build_aad(stanza_id, voter_jid) so the same canonical AAD is
used everywhere (do not change decrypt_poll_vote's signature or its use of the
pre-derived encryption_key; keep decrypt_addon and message_secret untouched).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 74efce10-a892-4798-94e2-97e6938e5a09

📥 Commits

Reviewing files that changed from the base of the PR and between 88a0fe0 and 8ee7128.

📒 Files selected for processing (8)
  • src/features/message_edit.rs
  • src/features/mod.rs
  • src/features/polls.rs
  • src/lib.rs
  • wacore/src/lib.rs
  • wacore/src/message_edit.rs
  • wacore/src/poll.rs
  • wacore/src/secret_enc_addon.rs

Comment thread src/features/message_edit.rs Outdated
Comment thread src/features/message_edit.rs Outdated
Comment thread wacore/src/poll.rs
Comment thread wacore/src/secret_enc_addon.rs
jlucaso1 added 2 commits May 11, 2026 10:28
…om_me

`EncryptedEdit::original_sender_jid` derived the original sender from
`participant || remote_jid`, which is correct for incoming 1:1 and group
edits but wrong for self-sent edits that arrive via device-sync: the
target message key has `from_me = true` and its `remote_jid` points to
the *other* party, not us. The HKDF info would then be keyed under the
wrong sender JID and the GCM tag check would fail.

WA Web sidesteps this because `MsgGetters.getOriginalSender` reads
`originalSelfAuthor || sender` from its materialised msg row, which
already carries the resolved self-author. We have no row, so we must
reconstruct the same fact from `from_me` plus the caller's own JID.

Updated resolution order:
1. `participant` (always set in groups)
2. `my_jid` when `from_me == Some(true)` (self-sent edit sync)
3. `remote_jid` (1:1 incoming edit, chat == other party)

`my_jid` is required as an argument. Added tests for the self-sent case
and the 1:1 incoming case to lock the resolution in.
Address review on PR #618.

- features/message_edit: drop the MessageEdits struct (all methods were
  associated; the stored Client ref was dead weight). Expose plain
  pub fn at module level and re-export EncryptedEdit from features.
  Drop Client::message_edits(); module is reachable via
  whatsapp_rust::features::message_edit.
- features/message_edit::extract_envelope: log::warn on malformed
  envelopes (missing fields or IV != 12 bytes) instead of silently
  returning None. Does not log enc_payload.
- wacore::poll: restore encrypt_poll_vote with the original pre-derived
  key signature, symmetric to decrypt_poll_vote. Delegates through the
  same AES-GCM helper + AAD as decrypt for byte-equivalent behaviour.
- wacore::poll: DRY the AAD construction by routing through
  secret_enc_addon::build_aad (now pub(crate)) for both decrypt_poll_vote
  and decrypt_poll_vote_with_secret. Extract a poll_vote_addon_ctx helper
  so the Context lives in one place.
- wacore::secret_enc_addon: build_aad is pub(crate).
- wacore::secret_enc_addon: rewrite message_edit_aad_is_empty_unlike_poll_vote
  (renamed aad_mismatch_under_same_key_fails_decrypt) to actually exercise
  the AAD branch: derive one key, encrypt under the PollVote AAD shape,
  and prove that decrypting the same ciphertext under the empty
  MessageEdit AAD fails on tag verification.

All workspace lib tests pass (1406 total). Zero clippy warnings.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/message_edit.rs`:
- Around line 73-110: decrypt_with_fallback currently normalizes JIDs and builds
a fallback MessageEditContext that can be byte-identical to the primary context,
causing a wasted second decryption; to fix, after constructing fb_orig/fb_editor
and before creating fallback_ctx compare the normalized fallback values
(fb_orig.as_deref(), fb_editor.as_deref()) against primary.original_sender_jid
and primary.editor_jid and set fallback_ctx to None when both match the primary
(i.e., skip creating a fallback MessageEditContext that equals primary), then
call message_edit::decrypt_message_edit_with_fallback with that possibly None
fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 88917a64-fd66-45f3-8abb-980d16f8a8b3

📥 Commits

Reviewing files that changed from the base of the PR and between 8ee7128 and e353376.

📒 Files selected for processing (5)
  • src/features/message_edit.rs
  • src/features/mod.rs
  • src/lib.rs
  • wacore/src/poll.rs
  • wacore/src/secret_enc_addon.rs

Comment thread src/features/message_edit.rs
`decrypt_with_fallback` previously built a fallback context whenever
either fallback JID was Some, including when those values normalised
(via `to_non_ad()`) to byte-identical strings as the primary context.
That second decrypt was guaranteed to fail the same way as the first.

Resolve fallback JIDs once, then short-circuit to `fallback_ctx = None`
when both resolved values equal the primary. Covers both the
no-fallback-supplied case and the "alternate-form normalises identical"
case in one branch.

Test: `fallback_normalising_to_primary_jids_is_skipped` proves the
no-op was elided by asserting the returned error is the bare primary
error rather than the combined `primary=...; fallback=...` shape that
`decrypt_message_edit_with_fallback` emits when both attempts run.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/message_edit.rs`:
- Around line 137-152: The match currently accepts Some(enc_payload) of zero
length as valid and constructs EncryptedEdit, which later fails during AES-GCM
tag extraction; update the pattern guard so the success arm only matches when
enc_payload is Some(payload) and payload.len() is at least the AES-GCM tag
length (e.g. >= 16) in addition to iv.len() == 12, so empty/too-short payloads
fall through to the warn! branch; reference the match over (target_key,
enc_payload, enc_iv), the success arm that returns Some(EncryptedEdit {
enc_payload, enc_iv, target_message_key: tk }) and the warn! fallback to ensure
malformed envelopes are logged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6df1f323-5aad-4daa-8298-f1a6bcf7cdad

📥 Commits

Reviewing files that changed from the base of the PR and between e353376 and 394d35a.

📒 Files selected for processing (1)
  • src/features/message_edit.rs

Comment on lines +137 to +152
match (target_key, enc_payload, enc_iv) {
(Some(tk), Some(payload), Some(iv)) if iv.len() == 12 => Some(EncryptedEdit {
enc_payload: payload,
enc_iv: iv,
target_message_key: tk,
}),
(tk, payload, iv) => {
warn!(
"secret_encrypted_message MESSAGE_EDIT malformed: target_id={:?} has_payload={} iv_len={:?} (expected 12)",
tk.and_then(|t| t.id.as_deref()),
payload.is_some(),
iv.map(|b| b.len()),
);
None
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Optional: empty enc_payload slips past the malformed-envelope warn.

A sender that ships enc_payload = Some(vec![]) (zero-length) with a 12-byte IV will be wrapped as a "valid" EncryptedEdit, then fail downstream in GCM tag extraction with a generic decrypt error — no warn! here, even though the envelope is structurally broken (AES-GCM ciphertext must carry the 16-byte tag). If you want the diagnostic-on-malformed path to cover this too, fold a minimum-length check into the same arm:

♻️ Optional tightening of envelope validation
     match (target_key, enc_payload, enc_iv) {
-        (Some(tk), Some(payload), Some(iv)) if iv.len() == 12 => Some(EncryptedEdit {
+        (Some(tk), Some(payload), Some(iv)) if iv.len() == 12 && payload.len() >= 16 => Some(EncryptedEdit {
             enc_payload: payload,
             enc_iv: iv,
             target_message_key: tk,
         }),
         (tk, payload, iv) => {
             warn!(
-                "secret_encrypted_message MESSAGE_EDIT malformed: target_id={:?} has_payload={} iv_len={:?} (expected 12)",
+                "secret_encrypted_message MESSAGE_EDIT malformed: target_id={:?} payload_len={:?} iv_len={:?} (expected payload>=16, iv=12)",
                 tk.and_then(|t| t.id.as_deref()),
-                payload.is_some(),
+                payload.map(|p| p.len()),
                 iv.map(|b| b.len()),
             );
             None
         }
     }

Not blocking — current behaviour is still safe, just diagnostically thinner on this one edge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/message_edit.rs` around lines 137 - 152, The match currently
accepts Some(enc_payload) of zero length as valid and constructs EncryptedEdit,
which later fails during AES-GCM tag extraction; update the pattern guard so the
success arm only matches when enc_payload is Some(payload) and payload.len() is
at least the AES-GCM tag length (e.g. >= 16) in addition to iv.len() == 12, so
empty/too-short payloads fall through to the warn! branch; reference the match
over (target_key, enc_payload, enc_iv), the success arm that returns
Some(EncryptedEdit { enc_payload, enc_iv, target_message_key: tk }) and the
warn! fallback to ensure malformed envelopes are logged.

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