Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/stellar-wallet-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "394xQCAQ/v69sB9OcdC5tUFRzUtDOhBdQ8lANM/r41k=",
"shasum": "XmHWNIVVLlhvKq+iksu3PyMYqx95Zj7lQAB/s1cOFlk=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ import { KeyringHandler } from './keyring';

jest.mock('../../utils/logger');
jest.mock('../../utils/snap');
jest.mock('../../utils/requestResponse', () => ({
...jest.requireActual('../../utils/requestResponse'),
jest.mock('@metamask/snap-networks-utils', () => ({
...jest.requireActual('@metamask/snap-networks-utils'),
validateOrigin: jest.fn(),
}));
jest.mock('@metamask/keyring-snap-sdk/v2', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from '@metamask/keyring-api';
import type { KeyringSnapRpc } from '@metamask/keyring-api/v2';
import { handleKeyringRequest } from '@metamask/keyring-snap-sdk/v2';
import { validateOrigin } from '@metamask/snap-networks-utils';
import type { Logger } from '@metamask/snap-networks-utils';
import { InvalidParamsError } from '@metamask/snaps-sdk';
import type { Json, JsonRpcRequest } from '@metamask/snaps-sdk';
Expand All @@ -27,6 +28,7 @@ import type {
KnownCaip2ChainId,
} from '../../api';
import { AppConfig } from '../../config';
import { originPermissions } from '../../permissions';
import type {
AccountService,
StellarKeyringAccount,
Expand All @@ -48,7 +50,6 @@ import {
getSlip44AssetId,
isClassicAssetId,
isSlip44Id,
validateOrigin,
validateRequest,
withCatchAndThrowSnapError,
} from '../../utils';
Expand Down Expand Up @@ -105,7 +106,7 @@ export class KeyringHandler implements KeyringSnapRpc {
origin,
method: request.method,
});
validateOrigin(origin, request.method);
validateOrigin(origin, request.method, originPermissions);
const keyringRequestResult = await handleKeyringRequest(this, request);
this.#logger.debug('Keyring request handled', {
origin,
Expand Down
33 changes: 16 additions & 17 deletions packages/stellar-wallet-snap/src/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { KeyringRpcMethod } from '@metamask/keyring-api';
import { KeyringSnapRpcMethod } from '@metamask/keyring-api/v2';
import {
createOriginPermissions,
DEFAULT_PROD_ORIGINS,
} from '@metamask/snap-networks-utils';

import { METAMASK_ORIGIN } from './constants';

const prodOrigins = ['https://portfolio.metamask.io'];
const allowedOrigins = prodOrigins;

/**
* Dapp origins are connected to the snap, but the snap does not expose any
* keyring method to them. The set is empty so every dapp call is rejected.
*/
const dappPermissions = new Set<string>([]);

const metamaskPermissions = new Set([
const metamaskMethods = [
KeyringSnapRpcMethod.GetAccounts,
KeyringSnapRpcMethod.GetAccount,
KeyringSnapRpcMethod.CreateAccounts,
Expand All @@ -31,11 +26,15 @@ const metamaskPermissions = new Set([
*/
KeyringRpcMethod.ListAccountAssets,
KeyringRpcMethod.ListAccountTransactions,
]);
];

export const originPermissions = new Map<string, Set<string>>([]);

for (const origin of allowedOrigins) {
originPermissions.set(origin, dappPermissions);
}
originPermissions.set(METAMASK_ORIGIN, metamaskPermissions);
/**
* Dapp origins are connected to the snap, but the snap does not expose any
* keyring method to them. `dappMethods` is empty so every dapp call is rejected.
*/
export const originPermissions = createOriginPermissions({
dappMethods: [],
metamaskMethods,
origins: DEFAULT_PROD_ORIGINS,
metamaskOrigin: METAMASK_ORIGIN,
});
30 changes: 19 additions & 11 deletions packages/stellar-wallet-snap/src/utils/requestResponse.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { KeyringRpcMethod } from '@metamask/keyring-api';
import { KeyringSnapRpcMethod } from '@metamask/keyring-api/v2';
import { validateOrigin } from '@metamask/snap-networks-utils';
import {
InvalidParamsError,
SnapError,
Expand All @@ -8,11 +9,8 @@ import {
import { string, object } from '@metamask/superstruct';

import { METAMASK_ORIGIN } from '../constants';
import {
validateRequest,
validateResponse,
validateOrigin,
} from './requestResponse';
import { originPermissions } from '../permissions';
import { validateRequest, validateResponse } from './requestResponse';

const TestStruct = object({
url: string(),
Expand Down Expand Up @@ -55,9 +53,9 @@ describe('validateOrigin', () => {
KeyringSnapRpcMethod.CreateAccounts,
KeyringSnapRpcMethod.SubmitRequest,
])('rejects method %s for dapps', (method) => {
expect(() => validateOrigin('http://localhost:3000', method)).toThrow(
UnauthorizedError,
);
expect(() =>
validateOrigin('http://localhost:3000', method, originPermissions),
).toThrow(UnauthorizedError);
});

it.each([
Expand All @@ -67,7 +65,11 @@ describe('validateOrigin', () => {
KeyringRpcMethod.ListAccountAssets,
])('rejects method %s for the connected dapp origin', (method) => {
expect(() =>
validateOrigin('https://portfolio.metamask.io', method),
validateOrigin(
'https://portfolio.metamask.io',
method,
originPermissions,
),
).toThrow(UnauthorizedError);
});

Expand All @@ -85,14 +87,20 @@ describe('validateOrigin', () => {
])('allows method %s for metamask', (method) => {
const origin = METAMASK_ORIGIN;

expect(() => validateOrigin(origin, method)).not.toThrow();
expect(() =>
validateOrigin(origin, method, originPermissions),
).not.toThrow();
});

it.each(['invalid', undefined, '', null])(
'rejects unauthorized origin %s',
(origin) => {
expect(() =>
validateOrigin(origin as string, KeyringSnapRpcMethod.GetAccounts),
validateOrigin(
origin as string,
KeyringSnapRpcMethod.GetAccounts,
originPermissions,
),
).toThrow(UnauthorizedError);
},
);
Expand Down
25 changes: 1 addition & 24 deletions packages/stellar-wallet-snap/src/utils/requestResponse.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,8 @@
/* eslint-disable @typescript-eslint/only-throw-error */
import {
InvalidParamsError,
SnapError,
UnauthorizedError,
} from '@metamask/snaps-sdk';
import { InvalidParamsError, SnapError } from '@metamask/snaps-sdk';
import type { Struct } from '@metamask/superstruct';
import { assert, create } from '@metamask/superstruct';

import { originPermissions } from '../permissions';

/**
* Validates that the origin is allowed to make the request.
* If the origin is not found or the method is not allowed, an UnauthorizedError is thrown.
*
* @param origin - The origin of the request.
* @param method - The method of the request.
* @throws {UnauthorizedError} If the origin is not found or the method is not allowed.
*/
export const validateOrigin = (origin: string, method: string): void => {
if (!origin) {
throw new UnauthorizedError('Origin not found');
}
if (!originPermissions.get(origin)?.has(method)) {
throw new UnauthorizedError('Permission denied');
}
};

/**
* Validates that the request parameters conform to the expected structure defined by the provided struct.
* Returns the validated (and coerced) value so handlers receive the correct types.
Expand Down