Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
167 changes: 167 additions & 0 deletions app/scripts/lib/ppom/ppom-util.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SignatureController,
SignatureControllerState,
SignatureRequest,
SignatureRequestType,
SignatureStateChange,
} from '@metamask/signature-controller';
import { Hex, JsonRpcRequest } from '@metamask/utils';
Expand Down Expand Up @@ -44,6 +45,10 @@ const CHAIN_ID_MOCK = '0x1' as Hex;
const GAS_MOCK = '0x1234';
const GAS_PRICE_MOCK = '0x5678';

// Mock data for permission origin tests
const PERMISSION_ORIGIN_MOCK = 'https://malicious-site.com';
const GATOR_SNAP_ORIGIN_MOCK = 'local:http://localhost:8082';

const REQUEST_MOCK = {
method: 'eth_signTypedData_v4',
params: [],
Expand Down Expand Up @@ -475,6 +480,168 @@ describe('PPOM Utils', () => {
test2: undefined,
});
});

describe('permission origin handling', () => {
it('uses decodedPermission.origin for permission requests', async () => {
const request = {
...REQUEST_MOCK,
method: 'eth_signTypedData_v4',
origin: GATOR_SNAP_ORIGIN_MOCK, // Gator snap origin
};

const signatureRequestWithPermission = {
id: 'test-id',
chainId: '0x1',
networkClientId: 'test-network',
status: 'unapproved',
time: Date.now(),
type: SignatureRequestType.TypedSign,
messageParams: {
from: '0x123',
data: 'test-data',
},
decodedPermission: {
origin: PERMISSION_ORIGIN_MOCK, // Actual malicious domain
permission: {
type: 'native-token-stream',
data: {
initialAmount: '0x1234',
maxAmount: '0x1234',
amountPerSecond: '0x1234',
startTime: 123456789,
},
justification: 'Test permission',
},
chainId: '0x1',
signer: {
type: 'account',
data: { address: '0x123' },
},
expiry: 123456789,
},
} as SignatureRequest;

updateSecurityAlertResponseMock.mockResolvedValue(
signatureRequestWithPermission,
);

await validateRequestWithPPOM({
...validateRequestWithPPOMOptionsBase,
ppomController,
request,
});

expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1);
expect(ppom.validateJsonRpc).toHaveBeenCalledWith(
expect.objectContaining({
origin: PERMISSION_ORIGIN_MOCK, // Should use permission origin
}),
);
expect(ppom.validateJsonRpc).toHaveBeenCalledWith(
expect.not.objectContaining({
origin: GATOR_SNAP_ORIGIN_MOCK, // Should not use Gator snap origin
}),
);
});

it('falls back to request origin for non-permission requests', async () => {
const requestOrigin = 'https://dapp.com';
const request = {
...REQUEST_MOCK,
method: 'eth_signTypedData_v4',
origin: requestOrigin,
};

const signatureRequestWithoutPermission = {
id: 'test-id',
chainId: '0x1',
networkClientId: 'test-network',
status: 'unapproved',
time: Date.now(),
type: SignatureRequestType.TypedSign,
messageParams: {
from: '0x123',
data: 'test-data',
},
// No decodedPermission
} as SignatureRequest;

updateSecurityAlertResponseMock.mockResolvedValue(
signatureRequestWithoutPermission,
);

await validateRequestWithPPOM({
...validateRequestWithPPOMOptionsBase,
ppomController,
request,
});

expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1);
expect(ppom.validateJsonRpc).toHaveBeenCalledWith(
expect.objectContaining({
origin: requestOrigin, // Should use request origin
}),
);
});

it('handles malicious domain detection for permission requests', async () => {
const maliciousDomain = 'https://phishing-site.com';
const request = {
...REQUEST_MOCK,
method: 'eth_signTypedData_v4',
origin: GATOR_SNAP_ORIGIN_MOCK,
};

const signatureRequestWithMaliciousPermission = {
id: 'test-id',
chainId: '0x1',
networkClientId: 'test-network',
status: 'unapproved',
time: Date.now(),
type: SignatureRequestType.TypedSign,
messageParams: {
from: '0x123',
data: 'test-data',
},
decodedPermission: {
origin: maliciousDomain, // Malicious domain
permission: {
type: 'native-token-stream',
data: {
initialAmount: '0x1234',
maxAmount: '0x1234',
amountPerSecond: '0x1234',
startTime: 123456789,
},
justification: 'Suspicious permission request',
},
chainId: '0x1',
signer: {
type: 'account',
data: { address: '0x123' },
},
expiry: 123456789,
},
} as SignatureRequest;

updateSecurityAlertResponseMock.mockResolvedValue(
signatureRequestWithMaliciousPermission,
);

await validateRequestWithPPOM({
...validateRequestWithPPOMOptionsBase,
ppomController,
request,
});

expect(ppom.validateJsonRpc).toHaveBeenCalledTimes(1);
expect(ppom.validateJsonRpc).toHaveBeenCalledWith(
expect.objectContaining({
origin: maliciousDomain, // Should validate against malicious domain
}),
);
});
});
});

describe('generateSecurityAlertId', () => {
Expand Down
13 changes: 12 additions & 1 deletion app/scripts/lib/ppom/ppom-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,23 @@ function normalizePPOMRequest(
const { delegationMock, id, jsonrpc, method, origin, params } =
normalizedRequest;

// In case of gator permissions the origin is always gator snap.
// That is why we extract the actual origin from the decoded permission.
let actualOrigin = origin;
if (
controllerObject &&
'decodedPermission' in controllerObject &&
controllerObject.decodedPermission?.origin
) {
actualOrigin = controllerObject.decodedPermission.origin;
}

return {
delegationMock,
id,
jsonrpc,
method,
origin,
origin: actualOrigin, // Use permission origin if available
params,
};
}
Expand Down
Loading