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
4 changes: 4 additions & 0 deletions packages/eth-json-rpc-middleware/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Add more strict validation for signTypedData V4 requests ([#8526](https://github.com/MetaMask/core/pull/8526))

## [23.1.1]

### Changed
Expand Down
36 changes: 36 additions & 0 deletions packages/eth-json-rpc-middleware/src/utils/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
resemblesAddress,
validateAndNormalizeKeyholder,
validateParams,
validateTypedMessageKeys,
} from './validation';

jest.mock('@metamask/superstruct', () => ({
Expand Down Expand Up @@ -125,4 +126,39 @@ describe('Validation Utils', () => {
`);
});
});

describe('validateTypedMessageKeys', () => {
it('does not throw for data with only schema-defined keys', () => {
const data = JSON.stringify({
types: {
EIP712Domain: [{ name: 'name', type: 'string' }],
},
});

expect(() => validateTypedMessageKeys(data)).not.toThrow();
});

it('throws for data with extraneous keys', () => {
const data = JSON.stringify({
types: {
EIP712Domain: [{ name: 'name', type: 'string' }],
},
primaryType: 'EIP712Domain',
domain: {},
message: {},
extraKey: 'unexpected',
});

expect(() => validateTypedMessageKeys(data)).toThrow('Invalid input.');
});

it('throws when data contains only extraneous keys', () => {
const data = JSON.stringify({
foo: 'bar',
baz: 123,
});

expect(() => validateTypedMessageKeys(data)).toThrow('Invalid input.');
});
});
});
21 changes: 21 additions & 0 deletions packages/eth-json-rpc-middleware/src/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { TYPED_MESSAGE_SCHEMA } from '@metamask/eth-sig-util';
import { providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type { Struct, StructError } from '@metamask/superstruct';
import { validate } from '@metamask/superstruct';
Expand Down Expand Up @@ -187,3 +188,23 @@ export function validateTypedDataForPrototypePollution(data: string): void {
checkObjectForPrototypePollution(message);
}
}

/**
* Validates that EIP-712 typed message data contains only keys defined in
* the TYPED_MESSAGE_SCHEMA from `@metamask/eth-sig-util`. Rejects messages
* with extraneous top-level keys.
*
* @param data - The stringified typed data to validate.
* @throws rpcErrors.invalidInput() if extraneous keys are detected.
*/
export function validateTypedMessageKeys(data: string): void {
const parsedData = parseTypedMessage(data);
const allowedKeys = new Set(Object.keys(TYPED_MESSAGE_SCHEMA.properties));
const hasExtraneousKey = Object.keys(parsedData).some(
(key) => !allowedKeys.has(key),
);

if (hasExtraneousKey) {
throw rpcErrors.invalidInput();
}
}
23 changes: 23 additions & 0 deletions packages/eth-json-rpc-middleware/src/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,29 @@ describe('wallet', () => {
engine.handle(...createHandleParams(payload)),
).rejects.toThrow('Invalid input.');
});

it('should throw if message data contains extraneous keys', async () => {
const getAccounts = async (): Promise<string[]> => testAddresses.slice();
const processTypedMessageV4 = async (): Promise<string> => testMsgSig;
const engine = JsonRpcEngineV2.create({
middleware: [
createWalletMiddleware({ getAccounts, processTypedMessageV4 }),
],
});

const messageParams = getMsgParams();
const payload = {
method: 'eth_signTypedData_v4',
params: [
testAddresses[0],
JSON.stringify({ ...messageParams, extraKey: 'unexpected' }),
],
};

await expect(
engine.handle(...createHandleParams(payload)),
).rejects.toThrow('Invalid input.');
});
});

describe('sign', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/eth-json-rpc-middleware/src/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
validateAndNormalizeKeyholder as validateKeyholder,
validateTypedDataForPrototypePollution,
validateTypedDataV1ForPrototypePollution,
validateTypedMessageKeys,
} from './utils/validation';

export type TransactionParams = {
Expand Down Expand Up @@ -408,6 +409,7 @@ export function createWalletMiddleware({

const address = await validateAndNormalizeKeyholder(params[0], context);
const message = normalizeTypedMessage(params[1]);
validateTypedMessageKeys(message);
validatePrimaryType(message);
validateVerifyingContract(message);
validateTypedDataForPrototypePollution(message);
Expand Down
Loading