Skip to content

fix(blocking): resolve LID/PN before is_blocked compares, fixing PN-query false negatives - #707

Merged
jlucaso1 merged 2 commits into
mainfrom
fix/is-blocked-resolve-lid-before-compare
Jun 4, 2026
Merged

fix(blocking): resolve LID/PN before is_blocked compares, fixing PN-query false negatives#707
jlucaso1 merged 2 commits into
mainfrom
fix/is-blocked-resolve-lid-before-compare

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

block() / unblock() always resolve the input JID to a LID before sending (resolve_lid_pn), so modern WhatsApp stores blocks keyed by LID and GetBlocklist returns <item jid=...@lid> entries whose .user is the LID number. But is_blocked compared entry.jid.user == jid.user against the caller's JID verbatim, with no LID/PN resolution. If the caller passes a PN JID (the natural input for a phone-number contact), jid.user is the phone number, which never equals the stored LID's user, so is_blocked returns false even when the contact is blocked: a silent false negative.

WA Web keeps the blocklist keyed by LID (WAWebBlocklistCollection, membership by id) and normalizes membership tests to LID first.

Fix

Resolve the queried JID to its LID/PN pair (the same get_lid_pn_entry mapping block() / unblock() use) and match each blocklist entry against the raw user plus the resolved LID and PN. Falls back to the raw user alone when no mapping exists, so a LID query keeps working and the behavior only improves for PN input.

The match itself is a small pure helper blocklist_contains(&[BlocklistEntry], &[&str]), so the LID/PN-resolution semantics are unit-testable without a server.

Tests

New pn_query_matches_lid_keyed_block_only_when_resolved: a LID-keyed block is found when the candidate set includes the resolved LID, missed when only the raw PN is given (the old bug), matched directly for a LID query, and an unrelated contact is not blocked.

cargo fmt --all
cargo clippy -p whatsapp-rust --all-targets -- -D warnings   # clean
cargo test -p whatsapp-rust --lib blocking   # pass (incl. 1 new)

Breaking

None. is_blocked keeps its signature; PN-input queries now resolve correctly, LID-input queries are unchanged.

block()/unblock() store blocks keyed by LID, so GetBlocklist returns LID-keyed entries. is_blocked compared the raw input .user with no resolution, so a PN-input query never matched a LID-keyed block and returned a false negative.

Resolve the queried JID to its LID/PN pair (same mapping block() uses) and match each entry against the raw user plus the resolved LID and PN, via a pure blocklist_contains() helper so the resolution is unit-testable.
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e99bbf74-7fc7-4ac3-bebc-9a31b6b91aa3

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7b11c and 9d0b23f.

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

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved blocklist matching so blocks apply correctly when users are identified via linked phone numbers or alternate account identifiers.
  • Tests
    • Added/expanded unit tests to verify correct matching across resolved identifier mappings and to prevent false positives.

Walkthrough

The blocklist matching logic now resolves phone-number→LID mappings and builds multiple candidate user identifiers from the queried JID before checking blocklist membership; a new blocklist_contains helper matches entries against those candidates. Tests cover PN→LID resolution, direct LID matching, and non-matches.

Changes

Blocklist Resolution Enhancement

Layer / File(s) Summary
is_blocked rewrite and blocklist_contains helper
src/features/blocking.rs
is_blocked fetches the blocklist, derives candidate users from the JID bare user plus optional LID/PN mapping via get_lid_pn_entry, and delegates matching to a new private blocklist_contains helper that checks whether any blocklist entry's jid.user equals one of the candidates.
Blocklist resolution test coverage
src/features/blocking.rs
Unit tests validate that a PN resolved to a LID matches a LID-keyed block only after resolution, raw PN alone does not match, direct LID matching succeeds, and unrelated contacts are not reported as blocked.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#487: Introduces or refines the get_lid_pn_entry helper used for PN→LID resolution in the blocking logic.
  • oxidezap/whatsapp-rust#120: Implements LID↔PN identity resolution infrastructure referenced by the blocklist resolution changes.
  • oxidezap/whatsapp-rust#464: Adds LidQuerySpec-based PN→LID lookup behavior that aligns with the PN resolution used in blocking checks.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: resolving LID/PN identifiers before is_blocked comparison to correct false negatives for phone-number queries.
Description check ✅ Passed The description is thorough and directly related to the changeset, explaining the problem, solution, tests, and confirming no breaking changes.
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 fix/is-blocked-resolve-lid-before-compare

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: 6b7b11c5eb

ℹ️ 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 thread src/features/blocking.rs Outdated
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,838 2,838 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,272 8,272 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,398 49,398 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 54,827 54,827 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,592 1,592 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,219 4,219 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 112,939 113,083 -0.1%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,231 1,656,234 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 650,248 650,192 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 873,742 873,813 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,081,558 2,081,591 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 747,435 747,431 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,330,808 1,330,761 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,375,022 4,374,226 +0.0%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 518,658 517,458 +0.2%
binary_benchmark::marshal_group::bench_marshal_allocating 45,401 45,401 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,451 45,451 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,354 66,354 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,512 43,512 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,507 45,507 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,930 4,930 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,961 4,961 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,732 6,732 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,529 528,529 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,150 528,150 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,396 529,396 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,786 5,417,786 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,043 5,362,043 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,336 13,276,336 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,283 48,283 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,344 48,344 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,668 66,668 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,286 8,286 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,142,819 4,144,534 -0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,133 100,133 +0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,263,702 4,263,702 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 99,803 99,803 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,262 210,238 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 511,917 511,990 -0.0%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,979,828 11,975,365 +0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,938,402 4,845,382 +1.9%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,397 2,043,397 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,950 37,950 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,616,076 3,616,076 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 223,140 223,150 -0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

@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: 2

🤖 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/blocking.rs`:
- Around line 93-97: The candidate users Vec is built with possible duplicates
because bare.user can equal mapping.as_ref().lid or .phone_number; update the
logic around where users is constructed (the block that currently pushes
entry.lid and entry.phone_number) to only push each resolved value if it is not
equal to bare.user and not already present in users (or replace the push logic
with a small dedup step like building users then calling dedup or using a
HashSet). Specifically modify the code that references mapping and entry
(entry.lid, entry.phone_number) so you skip adding duplicate strings before the
blocklist check.
- Line 92: Replace the .ok().flatten() swallow with proper error propagation:
call self.client.get_lid_pn_entry(&bare).await and use the ? operator to
propagate Err variants (so infrastructure/back-end errors return Err from the
current function), then handle the returned Option normally (None is acceptable
and means "no mapping"). In short, change the mapping resolution at the call
site (self.client.get_lid_pn_entry) to propagate errors instead of ignoring them
and only treat the resulting Option::None as a non-error 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: 20fbbccd-e382-4b58-b615-b713a62aa46c

📥 Commits

Reviewing files that changed from the base of the PR and between 0e3c598 and 6b7b11c.

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

Comment thread src/features/blocking.rs Outdated
Comment thread src/features/blocking.rs
Swallowing the resolution error with .ok().flatten() would fall back to the raw user and re-introduce the false negative this PR fixes when the backend is failing. Propagate Err; only Ok(None) (genuine absence) falls back to the raw user.
@jlucaso1
jlucaso1 merged commit e44ee46 into main Jun 4, 2026
17 checks passed
@jlucaso1
jlucaso1 deleted the fix/is-blocked-resolve-lid-before-compare branch June 4, 2026 11:35
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