Skip to content

feat: add address poisoning detection to PhishingController - #8171

Merged
AugmentedMode merged 26 commits into
mainfrom
feat/address-poisoning-detection
May 14, 2026
Merged

feat: add address poisoning detection to PhishingController#8171
AugmentedMode merged 26 commits into
mainfrom
feat/address-poisoning-detection

Conversation

@AugmentedMode

@AugmentedMode AugmentedMode commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Explanation

This PR adds address poisoning detection to @metamask/phishing-controller by:

  • Introducing a findSimilarAddresses utility that compares a candidate address against known recipients using prefix/suffix matching heuristics
  • Exposing a new PhishingController:checkAddressPoisoning messenger action that clients can call to check if a recipient address looks like a poisoning attempt
  • Hydrating and maintaining a set of known recipient addresses from:
    • Confirmed transactions via TransactionController state
    • Address book entries via AddressBookController state
  • Subscribing to state changes on both controllers to keep the known recipients set up to date
  • Returning match metadata (prefix/suffix match lengths, poisoning score, diff indices) so consumers can make informed decisions about how to warn users

References

  • Related to address poisoning attack vector (learn more)

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Adds new recipient-hydration logic driven by TransactionController/AddressBookController state changes plus a new messenger-exposed method, so bugs could impact performance or client warning behavior though no funds-moving logic is changed.

Overview
Adds address-poisoning detection to @metamask/phishing-controller by introducing findSimilarAddresses and a new messenger action/method PhishingController:checkAddressPoisoning that returns similarity metadata (prefix/suffix match lengths, score, diff indices).

PhishingController now depends on @metamask/address-book-controller, hydrates a set of known recipient addresses from confirmed transactions and address-book entries, and keeps it updated by subscribing to both controllers’ stateChange events (with patch-aware transaction updates and rebuild fallbacks).

Includes new unit tests for the address similarity utility and extensive controller tests covering hydration, incremental updates, and failure-path logging; also tightens API error-type checks in a few existing scan methods.

Reviewed by Cursor Bugbot for commit 306647c. Bugbot is set up for automated code reviews on this repo. Configure here.

@AugmentedMode
AugmentedMode requested review from a team as code owners March 10, 2026 20:27
@AugmentedMode AugmentedMode changed the title Feat/address poisoning detection feat: add address poisoning detection to PhishingController Mar 11, 2026
Comment thread packages/phishing-controller/src/PhishingController.ts
Comment thread packages/phishing-controller/src/PhishingController.ts
Comment thread packages/phishing-controller/src/PhishingController.ts
@AugmentedMode AugmentedMode self-assigned this Mar 24, 2026
@AugmentedMode AugmentedMode added the team-product-safety Push issues to Product Safety team label Mar 24, 2026
Comment thread packages/phishing-controller/src/PhishingController.ts Outdated
Comment thread packages/phishing-controller/src/PhishingController.ts Outdated
Comment thread packages/phishing-controller/src/PhishingController.ts Outdated
Comment thread packages/phishing-controller/src/PhishingController.test.ts
Comment thread packages/phishing-controller/src/address-poisoning.ts Outdated
Comment thread packages/phishing-controller/src/address-poisoning.test.ts Outdated
Comment thread packages/phishing-controller/src/address-poisoning.test.ts
try {
if (patches.some((patch) => patch.path[0] === 'transactions')) {
try {
this.#setKnownRecipientsFromTransactionState(_state);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this rebuilding the known recipients by iterating through every confirmed transaction every time there's a patch? If yes, surely there's a more reasonable alternative -- I can see this being expensive for power users.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good callout. I changed this to update transaction recipients incrementally from the relevant transaction patch instead of rebuilding from every confirmed transaction on each update. It also tracks recipients per transaction id with counts, so removals and duplicate recipients are handled correctly.

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4df8176. Configure here.

Comment thread packages/phishing-controller/src/PhishingController.ts Outdated
imblue-dabadee
imblue-dabadee previously approved these changes May 12, 2026
@AugmentedMode
AugmentedMode enabled auto-merge May 14, 2026 14:53
@AugmentedMode
AugmentedMode added this pull request to the merge queue May 14, 2026
Merged via the queue into main with commit f4829aa May 14, 2026
370 checks passed
@AugmentedMode
AugmentedMode deleted the feat/address-poisoning-detection branch May 14, 2026 15:00
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jul 30, 2026
…ts (MetaMask#9699)

## Explanation

The `PhishingController` address poisoning check
([MetaMask#8171](MetaMask#8171)) builds its
known-recipients set from confirmed transactions in
`TransactionController` state, using `txParams.to` as the recipient. For
ERC-20/ERC-721/ERC-1155 token transfer methods (`transfer`,
`transferFrom`, `safeTransferFrom`), `txParams.to` is the **token
contract**, not the address receiving the tokens — the actual recipient
is encoded in the calldata. As a result:

* A lookalike of the actual token recipient did **not** trigger a
poisoning warning (the address the user actually pays is missing from
the set).
* A lookalike of the token contract **did** trigger a warning (the
contract is not a recipient the user pays).

This was validated live against a dev build of the extension: after a
confirmed ERC-20 `transfer`, the token contract address appeared in the
known-recipients set while the decoded calldata recipient did not.

### Solution

`TransactionController` already solves this exact problem for
first-time-interaction checks with a private `getEffectiveRecipient`
helper that decodes the recipient from calldata for token transfer types
(falling back to `txParams.to` otherwise). This PR:

* Extracts `getEffectiveRecipient` from
`utils/first-time-interaction.ts` into a new shared `utils/recipient.ts`
and exports it from `@metamask/transaction-controller` (behavior
unchanged for the first-time-interaction flow).
* Uses it in `PhishingController.#getRecipientAddressesFromTransaction`
so confirmed token transfers contribute the **decoded recipient** to the
known-recipients set, replacing the token contract address.

Generic contract interactions are intentionally unchanged: their
`txParams.to` is still added, since there is no decodable recipient to
substitute.


## Manual testing

1. In `metamask-extension/package.json`, add both Core preview packages
to `resolutions`:

   ```json
"@metamask/phishing-controller":
"npm:@metamask-previews/phishing-controller@17.3.0-preview-5da60b6",
"@metamask/transaction-controller":
"npm:@metamask-previews/transaction-controller@69.3.0-preview-5da60b6"
   ```

2. Run `yarn install` and start a development build of MetaMask
Extension.
3. Submit and confirm a standard ERC-20 `transfer` to a test recipient
address.
4. Inspect the address-poisoning known recipients after the transaction
is confirmed, or check lookalike candidates through
`PhishingController.checkAddressPoisoning`.
5. Confirm that a lookalike of the recipient encoded in the transfer
calldata matches the actual recipient and triggers address-poisoning
detection.
6. Confirm that a lookalike of the ERC-20 contract address does not
match the contract as a known recipient from this transfer.
7. Submit and confirm a generic contract interaction, then confirm its
`txParams.to` contract address is still added as a known recipient.

## References

* Related to [MetaMask#8171](MetaMask#8171)
(original address poisoning detection)
* Related to PSAFE-555

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes security-sensitive address-poisoning recipient hydration;
logic is covered by tests and reuses existing decoding with fallbacks to
`txParams.to`.
> 
> **Overview**
> **Address poisoning** now builds known recipients from the real payee
on confirmed ERC-20/ERC-721/ERC-1155 transfers, not the token contract
in `txParams.to`.
> 
> `getEffectiveRecipient` is moved out of first-time-interaction into
shared `utils/recipient.ts`, exported from
`@metamask/transaction-controller`, and wired into
`PhishingController.#getRecipientAddressesFromTransaction`.
First-time-interaction behavior is unchanged; generic contract
interactions still use `txParams.to`. Tests cover decoding and the
phishing integration case.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
5da60b6. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team-product-safety Push issues to Product Safety team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants