Skip to content

feat: Add support for using the messenger in preinstalled Snaps#4055

Open
FrederikBolding wants to merge 18 commits into
mainfrom
fb/messenger-endowment
Open

feat: Add support for using the messenger in preinstalled Snaps#4055
FrederikBolding wants to merge 18 commits into
mainfrom
fb/messenger-endowment

Conversation

@FrederikBolding

@FrederikBolding FrederikBolding commented Jun 30, 2026

Copy link
Copy Markdown
Member

Add support for using the messenger in preinstalled Snaps. To access the messenger a Snap must specify a list of actions in endowment:messenger. Then, it can use getMessenger to get a messenger object for use inside the Snap.

WIP

https://consensyssoftware.atlassian.net/browse/WPC-1021


Note

High Risk
Introduces a new path from Snap execution into the extension controller messenger; exposure is limited to preinstalled snaps, manifest-scoped actions, and a namespace blocklist, but misconfiguration or incomplete enforcement could still reach sensitive controllers.

Overview
Adds a messenger bridge so preinstalled Snaps can invoke selected MetaMask controller actions from snap code, gated by manifest permissions and RPC enforcement.

Snaps declare endowment:messenger with a messengerScopes caveat listing allowed action names (e.g. PhishingController:testOrigin). Manifest validation and permission plumbing register the new endowment, caveat type, and mapper alongside existing endowments.

The SDK exposes getMessenger(), which forwards messenger.call(...) to the new permitted method snap_messengerCall. The handler requires the snap to be preinstalled and to hold the messenger endowment, builds a scoped messenger via getMessenger, rejects calls into ApprovalController, KeyringController, and PermissionController, then dispatches the action.

snaps-simulation wires getMessenger to delegate only the permitted actions/events to the root controller messenger. The preinstalled example snap, jest tests, and test-snaps UI exercise PhishingController:testOrigin.

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

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.59%. Comparing base (52d3c27) to head (9f3f64c).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4055   +/-   ##
=======================================
  Coverage   98.59%   98.59%           
=======================================
  Files         425      428    +3     
  Lines       12413    12484   +71     
  Branches     1969     1975    +6     
=======================================
+ Hits        12238    12309   +71     
  Misses        175      175           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@FrederikBolding FrederikBolding changed the title feat: Add endowment:messenger feat: Add support for using the messenger in preinstalled Snaps Jul 1, 2026
@FrederikBolding

Copy link
Copy Markdown
Member Author

bugbot run

@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 98a2ae9. Configure here.

);
}

response.result = await snapMessenger.call(action, ...actionParams);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing messenger action allowlist

High Severity

snap_messengerCall loads allowed actions from the endowment:messenger caveat but never checks that the requested action is in that list before calling snapMessenger.call. Only a small hard-coded controller prefix block runs; any other messenger action could be invoked if the getMessenger hook exposes a broader messenger than intended.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 98a2ae9. Configure here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The assumption is that the messenger API handles that and that the hook only exposes a child messenger with the required actions/events.

/**
* Caveat specifying the required messenger actions and events by a Snap.
*/
MessengerScopes = 'messengerScopes',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Open to suggestions for a better name here

Comment thread packages/snaps-sdk/src/messenger.ts Outdated
Comment thread packages/snaps-sdk/src/messenger.ts Outdated
method: 'snap_messengerCall',
params: { action, params },
}),
} as AsyncMessenger<Action>;

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.

Why does this need a type cast?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Type 'Promise<Json>' is not assignable to type 'Promise<Awaited<ExtractActionResponse<Action, ActionType>>>'.
  Type 'Json' is not assignable to type 'Awaited<ExtractActionResponse<Action, ActionType>>'.
    Type 'null' is not assignable to type 'Awaited<ExtractActionResponse<Action, ActionType>>'

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.

Maybe cast the return type rather than the whole object. Slightly cleaner imo.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I couldn't find a clean way to do that in c8e95fb 🤔

Comment thread packages/snaps-utils/src/manifest/validation.ts Outdated
Comment thread packages/snaps-sdk/src/messenger.ts Outdated
@FrederikBolding FrederikBolding requested a review from Mrtenz July 2, 2026 09:12
@FrederikBolding

Copy link
Copy Markdown
Member Author

@metamaskbot update-pr

@FrederikBolding FrederikBolding marked this pull request as ready for review July 2, 2026 12:18
@FrederikBolding FrederikBolding requested a review from a team as a code owner July 2, 2026 12:18

const actions = getMessengerCaveatActions(permission);

const snapMessenger = getMessenger({ actions });

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.

Curious how you're planning to implement getMessenger? Can we scope that in a way that it only exposes the actions required by the Snap, or will snaps-rpc-methods be able to arbitrarily request actions?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

My current PoC defines it like this:

getMessenger: (actions, events) => {
    const messenger = new Messenger({
        namespace: `${origin}-messenger`,
        parent: this.controllerMessenger,
    });

    this.controllerMessenger.delegate({
        actions,
        events,
        messenger,
    });

    return messenger;
}

Though we could consider creating a PreinstalledSnapsMessenger to limit what can be delegated.

this.preinstalledSnapsMessenger = this.controllerMessenger.buildChild(...);

getMessenger: (actions, events) => {
    const messenger = new Messenger({
        namespace: `${origin}-messenger`,
        parent: this.preinstalledSnapsMessenger,
    });

    this.preinstalledSnapsMessenger.delegate({
        actions,
        events,
        messenger,
    });

    return messenger;
}

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.

How do either of these limit what can be delegated? 🤔

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The current (top) one does not, but the second one would limit it to the actions that are delegated to PreinstalledSnapsMessenger, which could be a static list in the clients.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Though the downside would be couldn't use new actions in an OTA for example

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.

3 participants