diff --git a/packages/snaps-controllers/src/snaps/SnapController.test.tsx b/packages/snaps-controllers/src/snaps/SnapController.test.tsx index 47b93595fd..1a5a062bfd 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.test.tsx +++ b/packages/snaps-controllers/src/snaps/SnapController.test.tsx @@ -6087,6 +6087,94 @@ describe('SnapController', () => { snapController.destroy(); }); + it('supports preinstalled snaps with two-way initial connections', async () => { + const rootMessenger = getControllerMessenger(); + jest.spyOn(rootMessenger, 'call'); + + rootMessenger.registerActionHandler( + 'PermissionController:getPermissions', + (origin) => { + if (origin === `${MOCK_SNAP_ID}2`) { + return { + [WALLET_SNAP_PERMISSION_KEY]: { + caveats: [ + { + type: SnapCaveatType.SnapIds, + value: { + [MOCK_SNAP_ID]: {}, + }, + }, + ], + date: 1664187844588, + id: 'izn0WGUO8cvq_jqvLQuQP', + invoker: MOCK_ORIGIN, + parentCapability: WALLET_SNAP_PERMISSION_KEY, + }, + }; + } + + return {}; + }, + ); + + const preinstalledSnaps = [ + { + snapId: MOCK_SNAP_ID, + manifest: getSnapManifest({ + initialConnections: { + [`${MOCK_SNAP_ID}2`]: {}, + }, + }), + files: [ + { + path: DEFAULT_SOURCE_PATH, + value: stringToBytes(DEFAULT_SNAP_BUNDLE), + }, + { + path: DEFAULT_ICON_PATH, + value: stringToBytes(DEFAULT_SNAP_ICON), + }, + ], + }, + { + snapId: `${MOCK_SNAP_ID}2` as SnapId, + manifest: getSnapManifest({ + initialConnections: { + [MOCK_SNAP_ID]: {}, + }, + }), + files: [ + { + path: DEFAULT_SOURCE_PATH, + value: stringToBytes(DEFAULT_SNAP_BUNDLE), + }, + { + path: DEFAULT_ICON_PATH, + value: stringToBytes(DEFAULT_SNAP_ICON), + }, + ], + }, + ]; + + const snapControllerOptions = getSnapControllerWithEESOptions({ + preinstalledSnaps, + rootMessenger, + }); + const [snapController] = getSnapControllerWithEES(snapControllerOptions); + + expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith( + 'PermissionController:revokePermissions', + { [MOCK_SNAP_ID]: ['wallet_snap'] }, + ); + + expect(snapControllerOptions.messenger.call).not.toHaveBeenCalledWith( + 'PermissionController:revokePermissions', + { [`${MOCK_SNAP_ID}2`]: ['wallet_snap'] }, + ); + + snapController.destroy(); + }); + it('supports preinstalled snaps with initial connections', async () => { const rootMessenger = getControllerMessenger(); jest.spyOn(rootMessenger, 'call'); diff --git a/packages/snaps-controllers/src/snaps/SnapController.ts b/packages/snaps-controllers/src/snaps/SnapController.ts index 1cbebc0c16..56205d3e6d 100644 --- a/packages/snaps-controllers/src/snaps/SnapController.ts +++ b/packages/snaps-controllers/src/snaps/SnapController.ts @@ -936,7 +936,7 @@ export class SnapController extends BaseController< closeAllConnections, messenger, state, - dynamicPermissions = ['endowment:caip25'], + dynamicPermissions = ['endowment:caip25', 'wallet_snap'], environmentEndowmentPermissions = [], excludedPermissions = {}, idleTimeCheckInterval = inMilliseconds(5, Duration.Second), @@ -4305,6 +4305,10 @@ export class SnapController extends BaseController< // If the permission doesn't have dependencies, or if at least one of // its dependencies is desired, include it in the desired permissions. + // NOTE: This 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. + // TODO: Deal with this technical debt. if (!hasDependencies || hasDependency) { accumulator[permissionName] = oldPermissions[permissionName]; }