Skip to content

feat(confirmations): scan EIP-712 address fields via phishing controller - #34786

Open
wzrdk3lly wants to merge 3 commits into
MetaMask:mainfrom
wzrdk3lly:feat/core-signature-address-scan
Open

wzrdk3lly wants to merge 3 commits into
MetaMask:mainfrom
wzrdk3lly:feat/core-signature-address-scan

Conversation

@wzrdk3lly

@wzrdk3lly wzrdk3lly commented Aug 13, 2026 •

Copy link
Copy Markdown

Summary

Ticket: PSAFE-441

MetaMask validates eth_signTypedData_v3 and eth_signTypedData_v4 requests with PPOM, which inspects the address fields of a signature. PPOM's threat data is refreshed on a delay, so an address that has been flagged recently can still be reported as benign, and it does not cover every chain or protocol.

Separately, the real-time per-address scan (/address/evm/scan) is only applied to a few fields today: the token verifyingContract, permit spender, and delegation delegate. Addresses carried in any other field — recipients, makers, tokens, custom protocol fields — receive no real-time scan at all.

This change wires a schema-driven extractor (added to @metamask/phishing-controller in MetaMask/core#9875) that returns up to 10 distinct address-typed values found in a typed-data message, including nested structs and arrays, matched by declared type rather than field name. When PPOM has not already flagged the request, those addresses are passed to PhishingController.scanAddress. Results reuse the existing address cache, so fields already scanned elsewhere are not requested again. A confirmation alert surfaces any flagged address using the existing alert template, naming the flagged field and address. If some addresses could not be checked because the cap, depth limit, or work budget was reached, a separate caution is shown.

Changes

  • app/lib/address-scanning/scan-unvalidated-signature.ts (new) — extracts up to 10 address-typed fields and scans each via PhishingController.scanAddress after PPOM; excludes signer and zero address
  • app/components/Views/confirmations/hooks/alerts/useSignatureAddressAlerts.ts (new) — hook that reads scan results and returns alerts for malicious/warning addresses (naming the field) plus a caution when the walk was incomplete
  • app/lib/ppom/ppom-util.ts — calls scanUnvalidatedSignatureAddresses after PPOM for v3/v4 requests when PPOM has not flagged the request
  • app/components/Views/confirmations/constants/alerts.ts — adds SignatureAddressScanIncomplete, SignatureAddressTrustSignalMalicious, SignatureAddressTrustSignalWarning to AlertKeys
  • app/components/Views/confirmations/hooks/alerts/useConfirmationAlerts.ts — wires useSignatureAddressAlerts into useSignatureAlerts()
  • app/components/Views/confirmations/hooks/metrics/useConfirmationAlertMetrics.ts — adds metric entries for the three new alert keys
  • locales/languages/en.json — adds alert_system.signature_address_scan.* strings

Dependencies

Blocked on MetaMask/core#9875. The package.json dependency on @metamask/phishing-controller must be bumped to the version that exports extractSignatureAddresses before this can merge.

References

Test plan

  • Open a v3/v4 typed-data signature on a supported chain with a known-malicious address in a non-from field — confirm a Danger alert appears naming the field and shortened address
  • Open a permit-type signature — confirm no duplicate spender alert
  • Trigger a message with more than 10 distinct address fields — confirm the scan-incomplete caution appears
  • Confirm no alert regression on standard transaction confirmations
  • Verify metrics fire for the three new alert keys

Screenshots/Recordings

Both recordings sign the same typed-data request — the message carries a flagged address in a non-permit field.

Before

Baseline build. The flagged address raises no alert on the confirmation screen.

simulator.screen.before.mov

After

With this change, the same request shows a danger alert on the "Interacting with" row naming the field and address.

Simulator.screen.after.mov

Note

Medium Risk
Touches signature confirmation security alerts and PPOM post-validation scanning. Behavior is additive and gated, but incorrect extraction or alert mapping could hide or over-flag threats.

Overview
Adds real-time phishing scans for address-typed fields in eth_signTypedData_v3/v4 messages, covering nested structs and custom fields that PPOM or permit-only scans miss.

After PPOM, if the result is not already malicious or warning, scanUnvalidatedSignatureAddresses extracts addresses (excluding the signer) via extractSignatureAddresses and scans them through PhishingController. Confirmation UI then shows danger/warning alerts naming the field and address, plus a caution when the extractor hits its cap.

Gated on security alerts / Blockaid being enabled. Personal sign and typed-data v1 are skipped. Metrics keys are added for the three new alerts.

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

Wires the signature address scanning feature into the confirmation flow
using `extractSignatureAddresses` from `@metamask/phishing-controller`
rather than a local copy of the extractor.

After PPOM validates a v3/v4 typed-data signature request and returns
a non-malicious result, address-typed fields in the EIP-712 message are
extracted and scanned via `PhishingController.scanAddress`. Results are
surfaced as alerts via the new `useSignatureAddressAlerts` hook.

Note: requires a bump to the `@metamask/phishing-controller` version
that exports `extractSignatureAddresses` (pending MetaMask/core PR).

Related: MetaMask#34428
@wzrdk3lly
wzrdk3lly requested a review from a team as a code owner August 13, 2026 23:51
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit e9ad9bd. Configure here.

@matthewwalsh0
matthewwalsh0 self-requested a review August 14, 2026 11:01
matthewwalsh0
matthewwalsh0 previously approved these changes Aug 17, 2026
/**
* Generate trust-signal alerts for the address fields of a typed-data signature.
*/
export function useSignatureAddressAlerts(): Alert[] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we use something more explicit like useSignatureTrustSignalAlerts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Great point! I applied this change

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor, hook was renamed but not file itself.

Comment thread app/components/Views/confirmations/hooks/alerts/useSignatureAddressAlerts.ts Outdated
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 10, 2026
…12 typed-data scanning (MetaMask#10170)

## Explanation
Ticket:
[PSAFE-441](https://consensyssoftware.atlassian.net/browse/PSAFE-441)

Supersedes [MetaMask#9875](MetaMask#9875) (same
change, opened from a core branch so changelog CI can resolve the head).
Review comments on that PR were addressed there.

MetaMask validates `eth_signTypedData_v3` and `eth_signTypedData_v4`
requests with PPOM, which inspects the address fields of a signature.
PPOM's threat data is refreshed on a delay, so an address that has been
flagged recently can still be reported as benign, and it does not cover
every chain or protocol.

Separately, the real-time per-address scan (`/address/evm/scan`) is only
applied to a few fields today: the token `verifyingContract`, permit
`spender`, and delegation `delegate`. Addresses carried in any other
field — recipients, makers, tokens, custom protocol fields — receive no
real-time scan at all.

This change adds a schema-driven extractor that returns up to 10
distinct `address`-typed values found in a typed-data message (including
nested structs and arrays, matched by declared type rather than field
name), and reports when the message could not be fully walked because
the address cap, depth limit, or work budget was reached. When PPOM has
not already flagged the request, the extracted addresses are passed to
the real-time address scan. Results reuse the existing address cache, so
fields already scanned elsewhere are not requested again.

### What this exports

- `extractSignatureAddresses(typedData, options)` — walks
`types`/`primaryType`/`message`, returns `{ addresses, fields, overflow,
maxAddresses }`
- `ExtractedSignatureAddresses` — return type
- `ExtractSignatureAddressesOptions` — options: `exclude` (addresses to
skip, e.g. the signer), `excludeFields` (top-level field names to skip,
e.g. `spender` when already covered by an existing scan), `maxAddresses`
(optional cap override; default 10, hard ceiling 50)
- `DEFAULT_MAX_SIGNATURE_ADDRESSES` and
`MAX_SIGNATURE_ADDRESSES_CEILING` — exported so clients can word
overflow copy and stay aligned with the default/ceiling

### Safeguards

- Cap of 10 distinct addresses per message by default, overridable via
`maxAddresses` up to 50
- Max traversal depth of 12
- Max 5000 nodes visited
- `overflow: true` is returned whenever the walk is incomplete so
callers can surface a caution

## References

- Prior fork PR (same change): MetaMask#9875
- MetaMask Extension (client wiring): MetaMask/metamask-extension#45521
- MetaMask Mobile (client wiring): MetaMask/metamask-mobile#34786
- Prior implementation with local extractor copy (Extension):
MetaMask/metamask-extension#45058
- Prior implementation with local extractor copy (Mobile):
MetaMask/metamask-mobile#34428

## Changelog

See `packages/phishing-controller/CHANGELOG.md`.

## Checklist

- [x] Tests written and passing
- [x] Changelog updated
- [x] Exports added to `index.ts`
- [ ] Version bump (pending release cut)

[PSAFE-441]:
https://consensyssoftware.atlassian.net/browse/PSAFE-441?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Security-adjacent signing flow support: incorrect normalization or
missed addresses could weaken typed-data scanning when clients adopt the
export, though behavior is heavily tested and bounded with overflow
signaling.
> 
> **Overview**
> Adds **`extractSignatureAddresses`**, a schema-driven helper that
walks EIP-712 `types`/`primaryType`/`message` and collects distinct
**`address`-typed** values (including nested structs and arrays) for
downstream real-time address scanning—not hard-coded field names like
`spender` or `to`.
> 
> The function **normalizes** values the same way signing does
(hex/decimal, leading 20 bytes, lowercase dedupe), supports
**`exclude`**, top-level **`excludeFields`**, and a configurable
distinct-address cap (**default 10**, ceiling **50**). It returns
**`addresses`**, per-address **`fields`** for alert copy, and
**`overflow`** when traversal hits caps, depth (12), or a 5000-node
budget so clients can warn when not every address was collected.
**`DEFAULT_MAX_SIGNATURE_ADDRESSES`** and
**`MAX_SIGNATURE_ADDRESSES_CEILING`** are exported from **`index.ts`**
alongside the new types.
> 
> Ships **`signature-address-extraction.ts`**, a large Jest suite
(including **`@metamask/eth-sig-util`** parity checks), changelog entry,
and **`eth-sig-util`** as a **devDependency** only—no
**`PhishingController`** wiring in this PR.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
732e1c3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Alex Donesky <adonesky@gmail.com>

This branch has not been deployed

No deployments
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.

2 participants