From 727d3684ebafb00c46b012a42357b2bf0cd533aa Mon Sep 17 00:00:00 2001 From: Ogulcan Poyraz Date: Mon, 13 Jul 2026 11:25:24 +0300 Subject: [PATCH 01/11] feat(eth-json-rpc-middleware): validate eth_sendTransaction / eth_signTransaction params Reject requests whose top-level params contain keys outside the allowlisted transaction fields or nest beyond MAX_TRANSACTION_PARAM_DEPTH (10). Prevents downstream normalization / PPOM WASM from crashing with RangeError on deeply-nested junk fields and silently bypassing security scans. Mirrors the guardrail pattern introduced for eth_signTypedData_v4 in #8526. CONF-1662 --- packages/eth-json-rpc-middleware/CHANGELOG.md | 1 + .../src/utils/validation.test.ts | 125 ++++++++++++++++++ .../src/utils/validation.ts | 98 ++++++++++++++ .../src/wallet.test.ts | 117 ++++++++++++++++ .../eth-json-rpc-middleware/src/wallet.ts | 3 + 5 files changed, 344 insertions(+) diff --git a/packages/eth-json-rpc-middleware/CHANGELOG.md b/packages/eth-json-rpc-middleware/CHANGELOG.md index a6823832a4..0ff955199b 100644 --- a/packages/eth-json-rpc-middleware/CHANGELOG.md +++ b/packages/eth-json-rpc-middleware/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/json-rpc-engine` from `^10.2.4` to `^10.5.0` ([#8661](https://github.com/MetaMask/core/pull/8661), [#8746](https://github.com/MetaMask/core/pull/8746), [#8753](https://github.com/MetaMask/core/pull/8753)) - Bump `@metamask/message-manager` from `^14.1.1` to `^14.1.2` ([#8755](https://github.com/MetaMask/core/pull/8755)) diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts index 230b476a4c..198133b3cd 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts @@ -5,9 +5,11 @@ import { any, validate } from '@metamask/superstruct'; import type { WalletMiddlewareKeyValues } from '../wallet'; import { + MAX_TRANSACTION_PARAM_DEPTH, resemblesAddress, validateAndNormalizeKeyholder, validateParams, + validateTransactionParams, validateTypedMessageKeys, } from './validation'; @@ -278,4 +280,127 @@ describe('Validation Utils', () => { }); }); }); + + describe('validateTransactionParams', () => { + const VALID_FROM = '0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb'; + const VALID_TO = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + + it('does not throw for minimal valid params', () => { + expect(() => + validateTransactionParams({ from: VALID_FROM }), + ).not.toThrow(); + }); + + it('does not throw for the full allowlisted param set', () => { + expect(() => + validateTransactionParams({ + accessList: [ + { + address: VALID_TO, + storageKeys: ['0x00', '0x01'], + }, + ], + authorizationList: [ + { + chainId: '0x1', + address: VALID_TO, + nonce: '0x0', + }, + ], + chainId: '0x1', + data: '0x095ea7b3', + from: VALID_FROM, + gas: '0x5208', + gasLimit: '0x5208', + gasPrice: '0x1', + maxFeePerGas: '0x2', + maxPriorityFeePerGas: '0x1', + nonce: '0x0', + to: VALID_TO, + type: '0x2', + value: '0x0', + }), + ).not.toThrow(); + }); + + it.each([ + ['null', null], + ['undefined', undefined], + ['a string', 'not-an-object'], + ['a number', 42], + ['a boolean', true], + ['an array', [{ from: VALID_FROM }]], + ])('throws when params is %s', (_label, value) => { + expect(() => validateTransactionParams(value)).toThrow('Invalid input.'); + }); + + it('throws for an extraneous top-level key', () => { + expect(() => + validateTransactionParams({ + from: VALID_FROM, + to: VALID_TO, + extraKey: 'unexpected', + }), + ).toThrow('Invalid input.'); + }); + + it('throws for the incident repro payload (deeply-nested junk field)', () => { + let junk: Record = {}; + for (let i = 0; i < 1200; i++) { + junk = { b: junk }; + } + + expect(() => + validateTransactionParams({ + from: VALID_FROM, + to: VALID_TO, + value: '0x0', + data: '0x095ea7b3', + test: junk, + }), + ).toThrow('Invalid input.'); + }); + + it('throws when an allowlisted field nests beyond the depth limit', () => { + let deep: Record = { leaf: true }; + for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH + 5; i++) { + deep = { nested: deep }; + } + + expect(() => + validateTransactionParams({ + from: VALID_FROM, + data: deep as unknown as string, + }), + ).toThrow('Invalid input.'); + }); + + it('throws when a deeply-nested array exceeds the depth limit', () => { + let deepArray: unknown = 'leaf'; + for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH + 5; i++) { + deepArray = [deepArray]; + } + + expect(() => + validateTransactionParams({ + from: VALID_FROM, + accessList: deepArray as never, + }), + ).toThrow('Invalid input.'); + }); + + it('does not throw when params sit exactly at the depth limit', () => { + let deep: unknown = 'leaf'; + for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH - 1; i++) { + deep = { nested: deep }; + } + + expect(() => + validateTransactionParams({ + from: VALID_FROM, + data: deep, + }), + ).not.toThrow(); + }); + }); }); diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.ts b/packages/eth-json-rpc-middleware/src/utils/validation.ts index de3782fa67..04af3e6c6c 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.ts @@ -234,3 +234,101 @@ export function validateTypedMessageKeys(data: string): void { } } } + +/** + * Top-level keys explicitly permitted on `eth_sendTransaction` and + * `eth_signTransaction` params. Any additional top-level key causes the + * request to be rejected before it reaches downstream consumers such as PPOM + * or the Security Alerts API. + * + * Derived from the dapp-facing subset of `TransactionParams` in + * `@metamask/transaction-controller`. Internal-only fields + * (`estimateGasError`, `estimatedBaseFee`, `estimateSuggested`, + * `estimateUsed`, `gasUsed`) are intentionally omitted — dapps should not be + * able to inject them. + */ +export const ALLOWED_TRANSACTION_PARAM_KEYS = new Set([ + 'accessList', + 'authorizationList', + 'chainId', + 'data', + 'from', + 'gas', + 'gasLimit', + 'gasPrice', + 'maxFeePerGas', + 'maxPriorityFeePerGas', + 'nonce', + 'to', + 'type', + 'value', +]); + +/** + * Maximum nesting depth permitted anywhere inside a transaction params + * object. Legitimate params (including `accessList` and + * `authorizationList`) are at most ~4 levels deep. Anything beyond this is + * treated as a denial-of-service attempt against downstream normalization + * (which recurses and can overflow the call stack in native/WASM code). + */ +export const MAX_TRANSACTION_PARAM_DEPTH = 10; + +/** + * Recursively checks that a value does not nest beyond + * `MAX_TRANSACTION_PARAM_DEPTH`. + * + * @param value - The value to check. + * @param depth - The current depth. Callers should pass `0`. + * @throws rpcErrors.invalidInput() if the value nests too deeply. + */ +function assertMaxDepth(value: unknown, depth: number): void { + if (depth > MAX_TRANSACTION_PARAM_DEPTH) { + throw rpcErrors.invalidInput(); + } + + if (value === null || typeof value !== 'object') { + return; + } + + if (Array.isArray(value)) { + for (const item of value) { + assertMaxDepth(item, depth + 1); + } + return; + } + + for (const key of Object.getOwnPropertyNames( + value as Record, + )) { + assertMaxDepth((value as Record)[key], depth + 1); + } +} + +/** + * Validates that `eth_sendTransaction` / `eth_signTransaction` params contain + * only spec-defined top-level keys and no excessively-nested structures. + * + * This guards against malicious dapps attaching deeply-nested junk fields + * (e.g. `{ from, to, data, test: { b: { b: { b: /* ~1200 levels *\/ } } } }`) + * that would otherwise crash downstream normalization or PPOM WASM with a + * `RangeError: Maximum call stack size exceeded`, bypassing security checks. + * + * @param params - The transaction params object supplied by the dapp. + * @throws rpcErrors.invalidInput() if params is not a plain object, contains + * an extraneous top-level key, or nests beyond `MAX_TRANSACTION_PARAM_DEPTH`. + */ +export function validateTransactionParams(params: unknown): void { + if (params === null || typeof params !== 'object' || Array.isArray(params)) { + throw rpcErrors.invalidInput(); + } + + const hasExtraneousKey = Object.keys(params).some( + (key) => !ALLOWED_TRANSACTION_PARAM_KEYS.has(key), + ); + + if (hasExtraneousKey) { + throw rpcErrors.invalidInput(); + } + + assertMaxDepth(params, 0); +} diff --git a/packages/eth-json-rpc-middleware/src/wallet.test.ts b/packages/eth-json-rpc-middleware/src/wallet.test.ts index a9964c0d3a..25ed91e977 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.test.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.test.ts @@ -1,5 +1,6 @@ import { MessageTypes, TypedMessage } from '@metamask/eth-sig-util'; import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2'; +import type { Json } from '@metamask/utils'; import type { MessageParams, @@ -150,6 +151,64 @@ describe('wallet', () => { ); }); + it('throws when params contain an extraneous top-level key', async () => { + const getAccounts = async (): Promise => + testAddresses.slice(0, 2); + const processTransaction = async (): Promise => testTxHash; + const engine = JsonRpcEngineV2.create({ + middleware: [ + createWalletMiddleware({ getAccounts, processTransaction }), + ], + }); + const payload = { + method: 'eth_sendTransaction', + params: [ + { + from: testAddresses[0], + to: testAddresses[1], + extraKey: 'unexpected', + }, + ], + }; + + await expect( + engine.handle(...createHandleParams(payload)), + ).rejects.toThrow('Invalid input.'); + }); + + it('throws for the incident repro payload with deeply-nested junk', async () => { + const getAccounts = async (): Promise => + testAddresses.slice(0, 2); + const processTransaction = async (): Promise => testTxHash; + const engine = JsonRpcEngineV2.create({ + middleware: [ + createWalletMiddleware({ getAccounts, processTransaction }), + ], + }); + + let junk: Json = {}; + for (let i = 0; i < 1200; i++) { + junk = { b: junk }; + } + + const payload = { + method: 'eth_sendTransaction', + params: [ + { + from: testAddresses[0], + to: testAddresses[1], + value: '0x0', + data: '0x095ea7b3', + test: junk, + }, + ] as Json[], + }; + + await expect( + engine.handle(...createHandleParams(payload)), + ).rejects.toThrow('Invalid input.'); + }); + it('should not override other request params', async () => { const getAccounts = async (): Promise => testAddresses.slice(0, 2); @@ -287,6 +346,64 @@ describe('wallet', () => { 'The requested account and/or method has not been authorized by the user.', ); }); + + it('throws when params contain an extraneous top-level key', async () => { + const getAccounts = async (): Promise => + testAddresses.slice(0, 2); + const processSignTransaction = async (): Promise => testTxHash; + const engine = JsonRpcEngineV2.create({ + middleware: [ + createWalletMiddleware({ getAccounts, processSignTransaction }), + ], + }); + const payload = { + method: 'eth_signTransaction', + params: [ + { + from: testAddresses[0], + to: testAddresses[1], + extraKey: 'unexpected', + }, + ], + }; + + await expect( + engine.handle(...createHandleParams(payload)), + ).rejects.toThrow('Invalid input.'); + }); + + it('throws for the incident repro payload with deeply-nested junk', async () => { + const getAccounts = async (): Promise => + testAddresses.slice(0, 2); + const processSignTransaction = async (): Promise => testTxHash; + const engine = JsonRpcEngineV2.create({ + middleware: [ + createWalletMiddleware({ getAccounts, processSignTransaction }), + ], + }); + + let junk: Json = {}; + for (let i = 0; i < 1200; i++) { + junk = { b: junk }; + } + + const payload = { + method: 'eth_signTransaction', + params: [ + { + from: testAddresses[0], + to: testAddresses[1], + value: '0x0', + data: '0x095ea7b3', + test: junk, + }, + ] as Json[], + }; + + await expect( + engine.handle(...createHandleParams(payload)), + ).rejects.toThrow('Invalid input.'); + }); }); describe('signTypedData', () => { diff --git a/packages/eth-json-rpc-middleware/src/wallet.ts b/packages/eth-json-rpc-middleware/src/wallet.ts index b220be16e0..eed733a639 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.ts @@ -23,6 +23,7 @@ import { normalizeTypedMessage, parseTypedMessage } from './utils/normalize'; import { resemblesAddress, validateAndNormalizeKeyholder as validateKeyholder, + validateTransactionParams, validateTypedDataForPrototypePollution, validateTypedDataV1ForPrototypePollution, validateTypedMessageKeys, @@ -249,6 +250,7 @@ export function createWalletMiddleware({ } const params = request.params[0] as TransactionParams | undefined; + validateTransactionParams(params); const txParams: TransactionParams = { ...params, // Not using nullish coalescing, since `params` may be `null`. @@ -282,6 +284,7 @@ export function createWalletMiddleware({ } const params = request.params[0] as TransactionParams | undefined; + validateTransactionParams(params); const txParams: TransactionParams = { ...params, // Not using nullish coalescing, since `params` may be `null`. From 6ea6aa88ff31cff8506d3e07ab3390198c802f49 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Mon, 13 Jul 2026 11:41:32 +0300 Subject: [PATCH 02/11] Update --- packages/eth-json-rpc-middleware/src/index.test.ts | 1 + packages/eth-json-rpc-middleware/src/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/eth-json-rpc-middleware/src/index.test.ts b/packages/eth-json-rpc-middleware/src/index.test.ts index ff6ab44352..beece96550 100644 --- a/packages/eth-json-rpc-middleware/src/index.test.ts +++ b/packages/eth-json-rpc-middleware/src/index.test.ts @@ -296,6 +296,7 @@ describe('index module', () => { "createWalletMiddleware": [Function], "providerAsMiddleware": [Function], "providerAsMiddlewareV2": [Function], + "validateTransactionParams": [Function], } `); }); diff --git a/packages/eth-json-rpc-middleware/src/index.ts b/packages/eth-json-rpc-middleware/src/index.ts index 8efff9e55d..a933d1669d 100644 --- a/packages/eth-json-rpc-middleware/src/index.ts +++ b/packages/eth-json-rpc-middleware/src/index.ts @@ -35,4 +35,5 @@ export { } from './methods/wallet-get-supported-execution-permissions'; export * from './providerAsMiddleware'; export * from './retryOnEmpty'; +export { validateTransactionParams } from './utils/validation'; export * from './wallet'; From ab3586bf65a2866bc2e1d5a50c2b78cc9a6a217f Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Mon, 13 Jul 2026 11:45:28 +0300 Subject: [PATCH 03/11] Update changelog --- packages/eth-json-rpc-middleware/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/CHANGELOG.md b/packages/eth-json-rpc-middleware/CHANGELOG.md index 0ff955199b..e8bf13969b 100644 --- a/packages/eth-json-rpc-middleware/CHANGELOG.md +++ b/packages/eth-json-rpc-middleware/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. +- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. ([9482](https://github.com/MetaMask/core/pull/9482)) - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/json-rpc-engine` from `^10.2.4` to `^10.5.0` ([#8661](https://github.com/MetaMask/core/pull/8661), [#8746](https://github.com/MetaMask/core/pull/8746), [#8753](https://github.com/MetaMask/core/pull/8753)) - Bump `@metamask/message-manager` from `^14.1.1` to `^14.1.2` ([#8755](https://github.com/MetaMask/core/pull/8755)) From 56569acf00d0bf511e167f5203982f67d9d49132 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Mon, 13 Jul 2026 11:58:52 +0300 Subject: [PATCH 04/11] Fix changelog --- packages/eth-json-rpc-middleware/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/CHANGELOG.md b/packages/eth-json-rpc-middleware/CHANGELOG.md index e8bf13969b..3e2467f183 100644 --- a/packages/eth-json-rpc-middleware/CHANGELOG.md +++ b/packages/eth-json-rpc-middleware/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. ([9482](https://github.com/MetaMask/core/pull/9482)) +- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. ([#9482](https://github.com/MetaMask/core/pull/9482)) - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/json-rpc-engine` from `^10.2.4` to `^10.5.0` ([#8661](https://github.com/MetaMask/core/pull/8661), [#8746](https://github.com/MetaMask/core/pull/8746), [#8753](https://github.com/MetaMask/core/pull/8753)) - Bump `@metamask/message-manager` from `^14.1.1` to `^14.1.2` ([#8755](https://github.com/MetaMask/core/pull/8755)) From 7114e30366d06616a2f1261b6df0a18d6ea985e4 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Tue, 14 Jul 2026 13:58:27 +0300 Subject: [PATCH 05/11] Update --- packages/eth-json-rpc-middleware/CHANGELOG.md | 2 +- .../src/utils/validation.test.ts | 96 +++++++++--- .../src/utils/validation.ts | 139 +++++++----------- .../src/wallet.test.ts | 16 +- 4 files changed, 131 insertions(+), 122 deletions(-) diff --git a/packages/eth-json-rpc-middleware/CHANGELOG.md b/packages/eth-json-rpc-middleware/CHANGELOG.md index 3e2467f183..806b155806 100644 --- a/packages/eth-json-rpc-middleware/CHANGELOG.md +++ b/packages/eth-json-rpc-middleware/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params contain extraneous top-level keys or nest beyond `MAX_TRANSACTION_PARAM_DEPTH` (10). Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields and silently bypassing security scans. ([#9482](https://github.com/MetaMask/core/pull/9482)) +- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params do not match the transaction schema (extraneous top-level keys, ill-typed fields such as non-hex `to`/`data`, malformed `accessList` / `authorizationList` entries) or exceed `MAX_TRANSACTION_PARAMS_SIZE_BYTES` when serialized. Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields or padded payloads and silently bypassing security scans. ([#9482](https://github.com/MetaMask/core/pull/9482)) - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/json-rpc-engine` from `^10.2.4` to `^10.5.0` ([#8661](https://github.com/MetaMask/core/pull/8661), [#8746](https://github.com/MetaMask/core/pull/8746), [#8753](https://github.com/MetaMask/core/pull/8753)) - Bump `@metamask/message-manager` from `^14.1.1` to `^14.1.2` ([#8755](https://github.com/MetaMask/core/pull/8755)) diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts index 198133b3cd..17998eec66 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts @@ -5,7 +5,7 @@ import { any, validate } from '@metamask/superstruct'; import type { WalletMiddlewareKeyValues } from '../wallet'; import { - MAX_TRANSACTION_PARAM_DEPTH, + MAX_TRANSACTION_PARAMS_SIZE_BYTES, resemblesAddress, validateAndNormalizeKeyholder, validateParams, @@ -283,7 +283,14 @@ describe('Validation Utils', () => { describe('validateTransactionParams', () => { const VALID_FROM = '0xbe93f9bacbcffc8ee6663f2647917ed7a20a57bb'; - const VALID_TO = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; + const VALID_TO = '0xdac17f958d2ee523a2206206994597c13d831ec7'; + + beforeEach(() => { + const actual = jest.requireActual<{ + validate: typeof validate; + }>('@metamask/superstruct'); + validateMock.mockImplementation(actual.validate); + }); it('does not throw for minimal valid params', () => { expect(() => @@ -291,7 +298,7 @@ describe('Validation Utils', () => { ).not.toThrow(); }); - it('does not throw for the full allowlisted param set', () => { + it('does not throw for the full valid param set', () => { expect(() => validateTransactionParams({ accessList: [ @@ -331,7 +338,9 @@ describe('Validation Utils', () => { ['a boolean', true], ['an array', [{ from: VALID_FROM }]], ])('throws when params is %s', (_label, value) => { - expect(() => validateTransactionParams(value)).toThrow('Invalid input.'); + expect(() => validateTransactionParams(value)).toThrow( + /Invalid params|Invalid input/u, + ); }); it('throws for an extraneous top-level key', () => { @@ -341,7 +350,7 @@ describe('Validation Utils', () => { to: VALID_TO, extraKey: 'unexpected', }), - ).toThrow('Invalid input.'); + ).toThrow(/Invalid params/u); }); it('throws for the incident repro payload (deeply-nested junk field)', () => { @@ -358,47 +367,86 @@ describe('Validation Utils', () => { data: '0x095ea7b3', test: junk, }), - ).toThrow('Invalid input.'); + ).toThrow(/Invalid params|Invalid input/u); }); - it('throws when an allowlisted field nests beyond the depth limit', () => { - let deep: Record = { leaf: true }; - for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH + 5; i++) { - deep = { nested: deep }; - } + it('throws when a typed field has the wrong type', () => { + expect(() => + validateTransactionParams({ + from: VALID_FROM, + to: { nested: 'not-an-address' }, + }), + ).toThrow(/Invalid params/u); + }); + it('throws when `to` is not a hex address', () => { expect(() => validateTransactionParams({ from: VALID_FROM, - data: deep as unknown as string, + to: 'not-an-address', + }), + ).toThrow(/Invalid params/u); + }); + + it('throws when `data` is not a hex string', () => { + expect(() => + validateTransactionParams({ + from: VALID_FROM, + data: 1234 as unknown as string, + }), + ).toThrow(/Invalid params/u); + }); + + it('throws when `accessList` entries are malformed', () => { + expect(() => + validateTransactionParams({ + from: VALID_FROM, + accessList: [{ address: 'not-hex', storageKeys: 'not-an-array' }], + }), + ).toThrow(/Invalid params/u); + }); + + it('throws for a data-padding attack that passes the schema', () => { + const padded = `0x${'00'.repeat(MAX_TRANSACTION_PARAMS_SIZE_BYTES)}`; + + expect(() => + validateTransactionParams({ + from: VALID_FROM, + to: VALID_TO, + data: padded, }), ).toThrow('Invalid input.'); }); - it('throws when a deeply-nested array exceeds the depth limit', () => { - let deepArray: unknown = 'leaf'; - for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH + 5; i++) { - deepArray = [deepArray]; - } + it('throws for an accessList-padding attack that passes the schema', () => { + const padded = Array.from( + { length: Math.ceil(MAX_TRANSACTION_PARAMS_SIZE_BYTES / 64) }, + () => ({ + address: VALID_TO, + storageKeys: [`0x${'00'.repeat(32)}`], + }), + ); expect(() => validateTransactionParams({ from: VALID_FROM, - accessList: deepArray as never, + to: VALID_TO, + accessList: padded, }), ).toThrow('Invalid input.'); }); - it('does not throw when params sit exactly at the depth limit', () => { - let deep: unknown = 'leaf'; - for (let i = 0; i < MAX_TRANSACTION_PARAM_DEPTH - 1; i++) { - deep = { nested: deep }; - } + it('does not throw for a legitimate multi-entry accessList well under the size limit', () => { + const entries = Array.from({ length: 16 }, () => ({ + address: VALID_TO, + storageKeys: [`0x${'11'.repeat(32)}`, `0x${'22'.repeat(32)}`], + })); expect(() => validateTransactionParams({ from: VALID_FROM, - data: deep, + to: VALID_TO, + accessList: entries, }), ).not.toThrow(); }); diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.ts b/packages/eth-json-rpc-middleware/src/utils/validation.ts index 04af3e6c6c..7284908fbb 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.ts @@ -1,8 +1,9 @@ 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'; +import { array, object, optional, validate } from '@metamask/superstruct'; import type { Hex } from '@metamask/utils'; +import { HexAddressStruct, StrictHexStruct } from '@metamask/utils'; import type { WalletMiddlewareContext } from '../wallet'; import { parseTypedMessage } from './normalize'; @@ -235,100 +236,64 @@ export function validateTypedMessageKeys(data: string): void { } } -/** - * Top-level keys explicitly permitted on `eth_sendTransaction` and - * `eth_signTransaction` params. Any additional top-level key causes the - * request to be rejected before it reaches downstream consumers such as PPOM - * or the Security Alerts API. - * - * Derived from the dapp-facing subset of `TransactionParams` in - * `@metamask/transaction-controller`. Internal-only fields - * (`estimateGasError`, `estimatedBaseFee`, `estimateSuggested`, - * `estimateUsed`, `gasUsed`) are intentionally omitted — dapps should not be - * able to inject them. - */ -export const ALLOWED_TRANSACTION_PARAM_KEYS = new Set([ - 'accessList', - 'authorizationList', - 'chainId', - 'data', - 'from', - 'gas', - 'gasLimit', - 'gasPrice', - 'maxFeePerGas', - 'maxPriorityFeePerGas', - 'nonce', - 'to', - 'type', - 'value', -]); - -/** - * Maximum nesting depth permitted anywhere inside a transaction params - * object. Legitimate params (including `accessList` and - * `authorizationList`) are at most ~4 levels deep. Anything beyond this is - * treated as a denial-of-service attempt against downstream normalization - * (which recurses and can overflow the call stack in native/WASM code). - */ -export const MAX_TRANSACTION_PARAM_DEPTH = 10; - -/** - * Recursively checks that a value does not nest beyond - * `MAX_TRANSACTION_PARAM_DEPTH`. - * - * @param value - The value to check. - * @param depth - The current depth. Callers should pass `0`. - * @throws rpcErrors.invalidInput() if the value nests too deeply. - */ -function assertMaxDepth(value: unknown, depth: number): void { - if (depth > MAX_TRANSACTION_PARAM_DEPTH) { - throw rpcErrors.invalidInput(); - } - - if (value === null || typeof value !== 'object') { - return; - } - - if (Array.isArray(value)) { - for (const item of value) { - assertMaxDepth(item, depth + 1); - } - return; - } - - for (const key of Object.getOwnPropertyNames( - value as Record, - )) { - assertMaxDepth((value as Record)[key], depth + 1); - } -} +const AccessListEntryStruct = object({ + address: HexAddressStruct, + storageKeys: array(StrictHexStruct), +}); + +const AuthorizationListEntryStruct = object({ + address: HexAddressStruct, + chainId: StrictHexStruct, + nonce: StrictHexStruct, + r: optional(StrictHexStruct), + s: optional(StrictHexStruct), + yParity: optional(StrictHexStruct), +}); + +export const TransactionParamsStruct = object({ + accessList: optional(array(AccessListEntryStruct)), + authorizationList: optional(array(AuthorizationListEntryStruct)), + chainId: optional(StrictHexStruct), + data: optional(StrictHexStruct), + from: HexAddressStruct, + gas: optional(StrictHexStruct), + gasLimit: optional(StrictHexStruct), + gasPrice: optional(StrictHexStruct), + maxFeePerGas: optional(StrictHexStruct), + maxPriorityFeePerGas: optional(StrictHexStruct), + nonce: optional(StrictHexStruct), + to: optional(HexAddressStruct), + type: optional(StrictHexStruct), + value: optional(StrictHexStruct), +}); + +export const MAX_TRANSACTION_PARAMS_SIZE_BYTES = 128 * 1024; /** - * Validates that `eth_sendTransaction` / `eth_signTransaction` params contain - * only spec-defined top-level keys and no excessively-nested structures. + * Validates `eth_sendTransaction` / `eth_signTransaction` params against the + * standard transaction schema and rejects payloads whose serialized size + * exceeds `MAX_TRANSACTION_PARAMS_SIZE_BYTES`. * - * This guards against malicious dapps attaching deeply-nested junk fields - * (e.g. `{ from, to, data, test: { b: { b: { b: /* ~1200 levels *\/ } } } }`) - * that would otherwise crash downstream normalization or PPOM WASM with a - * `RangeError: Maximum call stack size exceeded`, bypassing security checks. + * Guards against two attack shapes: + * 1. Structural: extraneous top-level keys or ill-typed nested values + * (e.g. `{ from, to, data, test: { b: { b: ... } } }`) that would crash + * downstream normalization / PPOM WASM with `RangeError: Maximum call + * stack size exceeded`, silently bypassing security checks. + * 2. Size: valid-shaped but oversized payloads (e.g. `data` padded with + * millions of hex zeros, or `accessList` with millions of entries) that + * exhaust memory / stack in the same downstream code. * * @param params - The transaction params object supplied by the dapp. - * @throws rpcErrors.invalidInput() if params is not a plain object, contains - * an extraneous top-level key, or nests beyond `MAX_TRANSACTION_PARAM_DEPTH`. + * @throws rpcErrors.invalidInput() if params does not match the schema or + * exceeds the size limit. + * @throws rpcErrors.invalidParams() with a Superstruct failure summary if + * the schema mismatch is on a typed field. */ export function validateTransactionParams(params: unknown): void { - if (params === null || typeof params !== 'object' || Array.isArray(params)) { - throw rpcErrors.invalidInput(); - } - - const hasExtraneousKey = Object.keys(params).some( - (key) => !ALLOWED_TRANSACTION_PARAM_KEYS.has(key), - ); - - if (hasExtraneousKey) { + const serializedSize = JSON.stringify(params ?? null).length; + if (serializedSize > MAX_TRANSACTION_PARAMS_SIZE_BYTES) { throw rpcErrors.invalidInput(); } - assertMaxDepth(params, 0); + validateParams(params, TransactionParamsStruct); } diff --git a/packages/eth-json-rpc-middleware/src/wallet.test.ts b/packages/eth-json-rpc-middleware/src/wallet.test.ts index 25ed91e977..ee4550add9 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.test.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.test.ts @@ -116,9 +116,7 @@ describe('wallet', () => { method: 'eth_sendTransaction', params: [txParams], }); - await expect(engine.handle(payload)).rejects.toThrow( - new Error('Invalid parameters: must provide an Ethereum address.'), - ); + await expect(engine.handle(payload)).rejects.toThrow(/Invalid params/u); }); it('throws unauthorized for unknown addresses', async () => { @@ -173,7 +171,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow('Invalid input.'); + ).rejects.toThrow(/Invalid params/u); }); it('throws for the incident repro payload with deeply-nested junk', async () => { @@ -206,7 +204,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow('Invalid input.'); + ).rejects.toThrow(/Invalid params|Invalid input/u); }); it('should not override other request params', async () => { @@ -315,9 +313,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow( - new Error('Invalid parameters: must provide an Ethereum address.'), - ); + ).rejects.toThrow(/Invalid params/u); }); it('should throw when provided unknown address', async () => { @@ -369,7 +365,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow('Invalid input.'); + ).rejects.toThrow(/Invalid params/u); }); it('throws for the incident repro payload with deeply-nested junk', async () => { @@ -402,7 +398,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow('Invalid input.'); + ).rejects.toThrow(/Invalid params|Invalid input/u); }); }); From c8ec91868fe5a2e37ed4ec2b7559dc80e5d378af Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Tue, 14 Jul 2026 14:10:05 +0300 Subject: [PATCH 06/11] Update size --- .../src/utils/validation.test.ts | 34 +++++++++++- .../src/utils/validation.ts | 53 ++++++++++++++----- .../src/wallet.test.ts | 8 +-- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts index 17998eec66..caa86a8664 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts @@ -350,7 +350,7 @@ describe('Validation Utils', () => { to: VALID_TO, extraKey: 'unexpected', }), - ).toThrow(/Invalid params/u); + ).toThrow('Invalid input.'); }); it('throws for the incident repro payload (deeply-nested junk field)', () => { @@ -367,7 +367,37 @@ describe('Validation Utils', () => { data: '0x095ea7b3', test: junk, }), - ).toThrow(/Invalid params|Invalid input/u); + ).toThrow('Invalid input.'); + }); + + it('rejects an extraneous top-level key without walking its value (no JSON.stringify, no Superstruct)', () => { + const stringifySpy = jest.spyOn(JSON, 'stringify'); + validateMock.mockClear(); + + const params = { + from: VALID_FROM, + to: VALID_TO, + test: { + get b(): never { + throw new Error('subtree must not be walked'); + }, + }, + }; + + let thrown: unknown; + try { + validateTransactionParams(params); + } catch (error) { + thrown = error; + } + const stringifyCallsAtRejection = stringifySpy.mock.calls.length; + const validateCallsAtRejection = validateMock.mock.calls.length; + stringifySpy.mockRestore(); + + expect(thrown).toBeInstanceOf(Error); + expect((thrown as Error).message).toBe('Invalid input.'); + expect(stringifyCallsAtRejection).toBe(0); + expect(validateCallsAtRejection).toBe(0); }); it('throws when a typed field has the wrong type', () => { diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.ts b/packages/eth-json-rpc-middleware/src/utils/validation.ts index 7284908fbb..09ef69e0f2 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.ts @@ -267,6 +267,10 @@ export const TransactionParamsStruct = object({ value: optional(StrictHexStruct), }); +const ALLOWED_TRANSACTION_PARAM_KEYS = new Set( + Object.keys(TransactionParamsStruct.schema as Record), +); + export const MAX_TRANSACTION_PARAMS_SIZE_BYTES = 128 * 1024; /** @@ -274,24 +278,49 @@ export const MAX_TRANSACTION_PARAMS_SIZE_BYTES = 128 * 1024; * standard transaction schema and rejects payloads whose serialized size * exceeds `MAX_TRANSACTION_PARAMS_SIZE_BYTES`. * - * Guards against two attack shapes: - * 1. Structural: extraneous top-level keys or ill-typed nested values - * (e.g. `{ from, to, data, test: { b: { b: ... } } }`) that would crash - * downstream normalization / PPOM WASM with `RangeError: Maximum call - * stack size exceeded`, silently bypassing security checks. - * 2. Size: valid-shaped but oversized payloads (e.g. `data` padded with - * millions of hex zeros, or `accessList` with millions of entries) that - * exhaust memory / stack in the same downstream code. + * Checks run in this order to guarantee we never recurse into hostile + * subtrees: + * + * 1. Top-level shape: params must be a plain object whose top-level keys + * are all in the schema. Runs in O(top-level-keys) without visiting + * nested values, so a deeply-nested subtree under an extraneous key + * (e.g. `{ from, to, test: { b: { b: ... × 1200 } } }`) is rejected + * before any recursive walk can `RangeError`. + * 2. Serialized size: `JSON.stringify(params).length` must be + * `<= MAX_TRANSACTION_PARAMS_SIZE_BYTES`. Safe to walk at this point + * because step 1 guarantees the only nested values live under + * schema-declared fields (`accessList`, `authorizationList`) which are + * shallow arrays of flat objects. + * 3. Full schema validation: per-field types (hex strings, addresses, + * `accessList` / `authorizationList` entry shapes). + * + * Together these guard against: + * - Structural attacks: extraneous top-level keys or ill-typed nested + * values that would crash downstream normalization / PPOM WASM with + * `RangeError: Maximum call stack size exceeded`, silently bypassing + * security checks. + * - Size attacks: valid-shaped but oversized payloads (e.g. `data` padded + * with millions of hex zeros) that exhaust memory / stack in the same + * downstream code. * * @param params - The transaction params object supplied by the dapp. - * @throws rpcErrors.invalidInput() if params does not match the schema or - * exceeds the size limit. + * @throws rpcErrors.invalidInput() if params is not a plain object, + * contains an extraneous top-level key, or exceeds the size limit. * @throws rpcErrors.invalidParams() with a Superstruct failure summary if * the schema mismatch is on a typed field. */ export function validateTransactionParams(params: unknown): void { - const serializedSize = JSON.stringify(params ?? null).length; - if (serializedSize > MAX_TRANSACTION_PARAMS_SIZE_BYTES) { + if (params === null || typeof params !== 'object' || Array.isArray(params)) { + throw rpcErrors.invalidInput(); + } + + for (const key of Object.keys(params)) { + if (!ALLOWED_TRANSACTION_PARAM_KEYS.has(key)) { + throw rpcErrors.invalidInput(); + } + } + + if (JSON.stringify(params).length > MAX_TRANSACTION_PARAMS_SIZE_BYTES) { throw rpcErrors.invalidInput(); } diff --git a/packages/eth-json-rpc-middleware/src/wallet.test.ts b/packages/eth-json-rpc-middleware/src/wallet.test.ts index ee4550add9..2e5eeb381b 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.test.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.test.ts @@ -171,7 +171,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow(/Invalid params/u); + ).rejects.toThrow('Invalid input.'); }); it('throws for the incident repro payload with deeply-nested junk', async () => { @@ -204,7 +204,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow(/Invalid params|Invalid input/u); + ).rejects.toThrow('Invalid input.'); }); it('should not override other request params', async () => { @@ -365,7 +365,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow(/Invalid params/u); + ).rejects.toThrow('Invalid input.'); }); it('throws for the incident repro payload with deeply-nested junk', async () => { @@ -398,7 +398,7 @@ describe('wallet', () => { await expect( engine.handle(...createHandleParams(payload)), - ).rejects.toThrow(/Invalid params|Invalid input/u); + ).rejects.toThrow('Invalid input.'); }); }); From f9a7761090f34cc5a737e2815734d71529fe54f4 Mon Sep 17 00:00:00 2001 From: Goktug Poyraz Date: Tue, 14 Jul 2026 14:14:24 +0300 Subject: [PATCH 07/11] Update --- packages/eth-json-rpc-middleware/src/utils/validation.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.ts b/packages/eth-json-rpc-middleware/src/utils/validation.ts index 09ef69e0f2..17f6f199b1 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.ts @@ -271,7 +271,12 @@ const ALLOWED_TRANSACTION_PARAM_KEYS = new Set( Object.keys(TransactionParamsStruct.schema as Record), ); -export const MAX_TRANSACTION_PARAMS_SIZE_BYTES = 128 * 1024; +// Upper bound derived from the largest valid eth_sendTransaction payload: +// EIP-3860 caps initcode at 49,152 bytes → hex-encoded in 'data' field ≈ 98 KB of JSON. +// 200 KB is ~2× that ceiling, giving clear headroom above any protocol-legal +// transaction while blocking the padding attacks this cap defends against. +// TODO(CONF-1662): tighten once P99 production data is available. +export const MAX_TRANSACTION_PARAMS_SIZE_BYTES = 200 * 1024; /** * Validates `eth_sendTransaction` / `eth_signTransaction` params against the From 001ac90a4785a01662669cbb6ec8e5b4e62e3c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20G=C3=B6ktu=C4=9F=20Poyraz?= Date: Tue, 14 Jul 2026 18:31:56 +0300 Subject: [PATCH 08/11] Update packages/eth-json-rpc-middleware/src/wallet.test.ts Co-authored-by: Elliot Winkler --- packages/eth-json-rpc-middleware/src/wallet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/src/wallet.test.ts b/packages/eth-json-rpc-middleware/src/wallet.test.ts index 2e5eeb381b..dd64ba1347 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.test.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.test.ts @@ -174,7 +174,7 @@ describe('wallet', () => { ).rejects.toThrow('Invalid input.'); }); - it('throws for the incident repro payload with deeply-nested junk', async () => { + it('throws when params contain deeply nested invalid data', async () => { const getAccounts = async (): Promise => testAddresses.slice(0, 2); const processTransaction = async (): Promise => testTxHash; From 57f77c757274e95936184db33386637393337f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20G=C3=B6ktu=C4=9F=20Poyraz?= Date: Tue, 14 Jul 2026 18:32:05 +0300 Subject: [PATCH 09/11] Update packages/eth-json-rpc-middleware/src/wallet.test.ts Co-authored-by: Elliot Winkler --- packages/eth-json-rpc-middleware/src/wallet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/src/wallet.test.ts b/packages/eth-json-rpc-middleware/src/wallet.test.ts index dd64ba1347..fd6be12f8d 100644 --- a/packages/eth-json-rpc-middleware/src/wallet.test.ts +++ b/packages/eth-json-rpc-middleware/src/wallet.test.ts @@ -368,7 +368,7 @@ describe('wallet', () => { ).rejects.toThrow('Invalid input.'); }); - it('throws for the incident repro payload with deeply-nested junk', async () => { + it('throws when params contain deeply nested invalid data', async () => { const getAccounts = async (): Promise => testAddresses.slice(0, 2); const processSignTransaction = async (): Promise => testTxHash; From e8a1a2575ac32a4440898c15f4c9d155e6e8d07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20G=C3=B6ktu=C4=9F=20Poyraz?= Date: Tue, 14 Jul 2026 18:32:48 +0300 Subject: [PATCH 10/11] Update packages/eth-json-rpc-middleware/src/utils/validation.test.ts Co-authored-by: Elliot Winkler --- packages/eth-json-rpc-middleware/src/utils/validation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts index caa86a8664..1c56939d92 100644 --- a/packages/eth-json-rpc-middleware/src/utils/validation.test.ts +++ b/packages/eth-json-rpc-middleware/src/utils/validation.test.ts @@ -353,7 +353,7 @@ describe('Validation Utils', () => { ).toThrow('Invalid input.'); }); - it('throws for the incident repro payload (deeply-nested junk field)', () => { + it('throws given deeply-nested invalid data', () => { let junk: Record = {}; for (let i = 0; i < 1200; i++) { junk = { b: junk }; From e853f2f0931fc4443193af40263cbb472c93b088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20G=C3=B6ktu=C4=9F=20Poyraz?= Date: Tue, 14 Jul 2026 18:36:01 +0300 Subject: [PATCH 11/11] Update packages/eth-json-rpc-middleware/CHANGELOG.md Co-authored-by: Elliot Winkler --- packages/eth-json-rpc-middleware/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eth-json-rpc-middleware/CHANGELOG.md b/packages/eth-json-rpc-middleware/CHANGELOG.md index 806b155806..67f0fe1a32 100644 --- a/packages/eth-json-rpc-middleware/CHANGELOG.md +++ b/packages/eth-json-rpc-middleware/CHANGELOG.md @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params — reject requests whose params do not match the transaction schema (extraneous top-level keys, ill-typed fields such as non-hex `to`/`data`, malformed `accessList` / `authorizationList` entries) or exceed `MAX_TRANSACTION_PARAMS_SIZE_BYTES` when serialized. Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields or padded payloads and silently bypassing security scans. ([#9482](https://github.com/MetaMask/core/pull/9482)) +- **BREAKING:** Add strict validation for `eth_sendTransaction` and `eth_signTransaction` params ([#9482](https://github.com/MetaMask/core/pull/9482)) + - Reject requests whose params do not match the transaction schema (extraneous top-level keys, ill-typed fields such as non-hex `to`/`data`, malformed `accessList` / `authorizationList` entries) or exceed `MAX_TRANSACTION_PARAMS_SIZE_BYTES` when serialized + - Prevents downstream normalization / PPOM WASM from crashing on deeply-nested junk fields or padded payloads and silently bypassing security scans - Bump `@metamask/utils` from `^11.9.0` to `^11.11.0` ([#9074](https://github.com/MetaMask/core/pull/9074)) - Bump `@metamask/json-rpc-engine` from `^10.2.4` to `^10.5.0` ([#8661](https://github.com/MetaMask/core/pull/8661), [#8746](https://github.com/MetaMask/core/pull/8746), [#8753](https://github.com/MetaMask/core/pull/8753)) - Bump `@metamask/message-manager` from `^14.1.1` to `^14.1.2` ([#8755](https://github.com/MetaMask/core/pull/8755))