Skip to content

Commit 5426bc0

Browse files
fix: Prevent initial connections from being revoked as unused (#3729)
Fixes an issue where initial connections between Snaps could be considered unused when updating preinstalled Snaps. The fix is to treat `wallet_snap` as a dynamic permission, it should probably have been marked as one in the past when `wallet_requestSnaps` was unblocked since that allows dynamically adding to the permission. This in combination with a previous commit effectively means that any permissions granted in the manifest that are considered dynamic, will not be automatically revoked when the permission is removed from the manifest. This is intentional, but not ideal. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds `wallet_snap` to dynamic permissions to prevent revoking initial connections and introduces a test for two-way preinstalled snap connections. > > - **Controller**: > - Add `wallet_snap` to default `dynamicPermissions` in `SnapController` constructor to prevent unintended revocation of initial connections. > - **Tests**: > - Add test ensuring preinstalled snaps with two-way `initialConnections` do not trigger `PermissionController:revokePermissions` for `wallet_snap`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3435d5c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 82156c7 commit 5426bc0

File tree

2 files changed

+93
-1
lines changed

2 files changed

+93
-1
lines changed

packages/snaps-controllers/src/snaps/SnapController.test.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6087,6 +6087,94 @@ describe('SnapController', () => {
60876087
snapController.destroy();
60886088
});
60896089

6090+
it('supports preinstalled snaps with two-way initial connections', async () => {
6091+
const rootMessenger = getControllerMessenger();
6092+
jest.spyOn(rootMessenger, 'call');
6093+
6094+
rootMessenger.registerActionHandler(
6095+
'PermissionController:getPermissions',
6096+
(origin) => {
6097+
if (origin === `${MOCK_SNAP_ID}2`) {
6098+
return {
6099+
[WALLET_SNAP_PERMISSION_KEY]: {
6100+
caveats: [
6101+
{
6102+
type: SnapCaveatType.SnapIds,
6103+
value: {
6104+
[MOCK_SNAP_ID]: {},
6105+
},
6106+
},
6107+
],
6108+
date: 1664187844588,
6109+
id: 'izn0WGUO8cvq_jqvLQuQP',
6110+
invoker: MOCK_ORIGIN,
6111+
parentCapability: WALLET_SNAP_PERMISSION_KEY,
6112+
},
6113+
};
6114+
}
6115+
6116+
return {};
6117+
},
6118+
);
6119+
6120+
const preinstalledSnaps = [
6121+
{
6122+
snapId: MOCK_SNAP_ID,
6123+
manifest: getSnapManifest({
6124+
initialConnections: {
6125+
[`${MOCK_SNAP_ID}2`]: {},
6126+
},
6127+
}),
6128+
files: [
6129+
{
6130+
path: DEFAULT_SOURCE_PATH,
6131+
value: stringToBytes(DEFAULT_SNAP_BUNDLE),
6132+
},
6133+
{
6134+
path: DEFAULT_ICON_PATH,
6135+
value: stringToBytes(DEFAULT_SNAP_ICON),
6136+
},
6137+
],
6138+
},
6139+
{
6140+
snapId: `${MOCK_SNAP_ID}2` as SnapId,
6141+
manifest: getSnapManifest({
6142+
initialConnections: {
6143+
[MOCK_SNAP_ID]: {},
6144+
},
6145+
}),
6146+
files: [
6147+
{
6148+
path: DEFAULT_SOURCE_PATH,
6149+
value: stringToBytes(DEFAULT_SNAP_BUNDLE),
6150+
},
6151+
{
6152+
path: DEFAULT_ICON_PATH,
6153+
value: stringToBytes(DEFAULT_SNAP_ICON),
6154+
},
6155+
],
6156+
},
6157+
];
6158+
6159+
const snapControllerOptions = getSnapControllerWithEESOptions({
6160+
preinstalledSnaps,
6161+
rootMessenger,
6162+
});
6163+
const [snapController] = getSnapControllerWithEES(snapControllerOptions);
6164+
6165+
expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith(
6166+
'PermissionController:revokePermissions',
6167+
{ [MOCK_SNAP_ID]: ['wallet_snap'] },
6168+
);
6169+
6170+
expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith(
6171+
'PermissionController:revokePermissions',
6172+
{ [`${MOCK_SNAP_ID}2`]: ['wallet_snap'] },
6173+
);
6174+
6175+
snapController.destroy();
6176+
});
6177+
60906178
it('supports preinstalled snaps with initial connections', async () => {
60916179
const rootMessenger = getControllerMessenger();
60926180
jest.spyOn(rootMessenger, 'call');

packages/snaps-controllers/src/snaps/SnapController.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ export class SnapController extends BaseController<
936936
closeAllConnections,
937937
messenger,
938938
state,
939-
dynamicPermissions = ['endowment:caip25'],
939+
dynamicPermissions = ['endowment:caip25', 'wallet_snap'],
940940
environmentEndowmentPermissions = [],
941941
excludedPermissions = {},
942942
idleTimeCheckInterval = inMilliseconds(5, Duration.Second),
@@ -4305,6 +4305,10 @@ export class SnapController extends BaseController<
43054305

43064306
// If the permission doesn't have dependencies, or if at least one of
43074307
// its dependencies is desired, include it in the desired permissions.
4308+
// NOTE: This effectively means that any permissions granted in the manifest
4309+
// that are considered dynamic, will not be automatically revoked
4310+
// when the permission is removed from the manifest.
4311+
// TODO: Deal with this technical debt.
43084312
if (!hasDependencies || hasDependency) {
43094313
accumulator[permissionName] = oldPermissions[permissionName];
43104314
}

0 commit comments

Comments
 (0)