feat: Add support for using the messenger in preinstalled Snaps#4055
feat: Add support for using the messenger in preinstalled Snaps#4055FrederikBolding wants to merge 18 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
endowment:messenger|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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); |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit 98a2ae9. Configure here.
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
Open to suggestions for a better name here
| method: 'snap_messengerCall', | ||
| params: { action, params }, | ||
| }), | ||
| } as AsyncMessenger<Action>; |
There was a problem hiding this comment.
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>>'
There was a problem hiding this comment.
Maybe cast the return type rather than the whole object. Slightly cleaner imo.
There was a problem hiding this comment.
I couldn't find a clean way to do that in c8e95fb 🤔
|
@metamaskbot update-pr |
|
|
||
| const actions = getMessengerCaveatActions(permission); | ||
|
|
||
| const snapMessenger = getMessenger({ actions }); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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;
}There was a problem hiding this comment.
How do either of these limit what can be delegated? 🤔
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Though the downside would be couldn't use new actions in an OTA for example


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 usegetMessengerto 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:messengerwith amessengerScopescaveat 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 forwardsmessenger.call(...)to the new permitted methodsnap_messengerCall. The handler requires the snap to be preinstalled and to hold the messenger endowment, builds a scoped messenger viagetMessenger, rejects calls into ApprovalController, KeyringController, and PermissionController, then dispatches the action.snaps-simulation wires
getMessengerto delegate only the permitted actions/events to the root controller messenger. The preinstalled example snap, jest tests, and test-snaps UI exercisePhishingController:testOrigin.Reviewed by Cursor Bugbot for commit 98a2ae9. Bugbot is set up for automated code reviews on this repo. Configure here.