@metamask-previews/eth-json-rpc-middleware 23.1.2-preview-495c91e → 23.1.3-preview-1fba5d0b9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -1
- package/dist/utils/validation.cjs +20 -1
- package/dist/utils/validation.cjs.map +1 -1
- package/dist/utils/validation.d.cts.map +1 -1
- package/dist/utils/validation.d.mts.map +1 -1
- package/dist/utils/validation.mjs +20 -1
- package/dist/utils/validation.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [23.1.3]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Allow Advanced Permissions `metadata` in signTypedData V4 requests ([#8603](https://github.com/MetaMask/core/pull/8603))
|
|
15
|
+
|
|
10
16
|
## [23.1.2]
|
|
11
17
|
|
|
12
18
|
### Changed
|
|
@@ -89,7 +95,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
89
95
|
- See [`MetaMask/eth-json-rpc-middleware`](https://github.com/MetaMask/eth-json-rpc-middleware/blob/main/CHANGELOG.md)
|
|
90
96
|
for the original changelog.
|
|
91
97
|
|
|
92
|
-
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.1.
|
|
98
|
+
[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.1.3...HEAD
|
|
99
|
+
[23.1.3]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.1.2...@metamask/eth-json-rpc-middleware@23.1.3
|
|
93
100
|
[23.1.2]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.1.1...@metamask/eth-json-rpc-middleware@23.1.2
|
|
94
101
|
[23.1.1]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.1.0...@metamask/eth-json-rpc-middleware@23.1.1
|
|
95
102
|
[23.1.0]: https://github.com/MetaMask/core/compare/@metamask/eth-json-rpc-middleware@23.0.0...@metamask/eth-json-rpc-middleware@23.1.0
|
|
@@ -163,11 +163,30 @@ exports.validateTypedDataForPrototypePollution = validateTypedDataForPrototypePo
|
|
|
163
163
|
*/
|
|
164
164
|
function validateTypedMessageKeys(data) {
|
|
165
165
|
const parsedData = (0, normalize_1.parseTypedMessage)(data);
|
|
166
|
-
const allowedKeys = new Set(
|
|
166
|
+
const allowedKeys = new Set([
|
|
167
|
+
...Object.keys(eth_sig_util_1.TYPED_MESSAGE_SCHEMA.properties),
|
|
168
|
+
'metadata',
|
|
169
|
+
]);
|
|
167
170
|
const hasExtraneousKey = Object.keys(parsedData).some((key) => !allowedKeys.has(key));
|
|
168
171
|
if (hasExtraneousKey) {
|
|
169
172
|
throw rpc_errors_1.rpcErrors.invalidInput();
|
|
170
173
|
}
|
|
174
|
+
// Advanced Permissions adds `metadata: { justification: string, origin: string }` to eth_signTypedData requests.
|
|
175
|
+
// see GatorPermissionsController.decodePermissionFromPermissionContextForOrigin for more details.
|
|
176
|
+
const { metadata } = parsedData;
|
|
177
|
+
if (metadata !== undefined) {
|
|
178
|
+
if (typeof metadata !== 'object' || metadata === null) {
|
|
179
|
+
throw rpc_errors_1.rpcErrors.invalidInput();
|
|
180
|
+
}
|
|
181
|
+
const { justification, origin } = metadata;
|
|
182
|
+
if (typeof justification !== 'string' || typeof origin !== 'string') {
|
|
183
|
+
throw rpc_errors_1.rpcErrors.invalidInput();
|
|
184
|
+
}
|
|
185
|
+
// we only need to check the keys length, because we already checked the known keys (justification and origin).
|
|
186
|
+
if (Object.keys(metadata).length !== 2) {
|
|
187
|
+
throw rpc_errors_1.rpcErrors.invalidInput();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
171
190
|
}
|
|
172
191
|
exports.validateTypedMessageKeys = validateTypedMessageKeys;
|
|
173
192
|
//# sourceMappingURL=validation.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.cjs","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { TYPED_MESSAGE_SCHEMA } from '@metamask/eth-sig-util';\nimport { providerErrors, rpcErrors } from '@metamask/rpc-errors';\nimport type { Struct, StructError } from '@metamask/superstruct';\nimport { validate } from '@metamask/superstruct';\nimport type { Hex } from '@metamask/utils';\n\nimport type { WalletMiddlewareContext } from '../wallet';\nimport { parseTypedMessage } from './normalize';\n\n/**\n * Validates and normalizes a keyholder address for transaction- and\n * signature-related operations.\n *\n * @param address - The Ethereum address to validate and normalize.\n * @param context - The context of the request.\n * @param options - The options for the validation.\n * @param options.getAccounts - The function to get the accounts for the origin.\n * @returns The normalized address, if valid. Otherwise, throws\n * an error\n */\nexport async function validateAndNormalizeKeyholder(\n address: Hex,\n context: WalletMiddlewareContext,\n { getAccounts }: { getAccounts: (origin: string) => Promise<string[]> },\n): Promise<Hex> {\n if (\n typeof address === 'string' &&\n address.length > 0 &&\n resemblesAddress(address)\n ) {\n // Ensure that an \"unauthorized\" error is thrown if the requester\n // does not have the `eth_accounts` permission.\n const accounts = await getAccounts(context.assertGet('origin'));\n\n const normalizedAccounts: string[] = accounts.map((_address) =>\n _address.toLowerCase(),\n );\n\n const normalizedAddress = address.toLowerCase() as Hex;\n\n if (normalizedAccounts.includes(normalizedAddress)) {\n return normalizedAddress;\n }\n\n throw providerErrors.unauthorized();\n }\n\n throw rpcErrors.invalidParams({\n message: `Invalid parameters: must provide an Ethereum address.`,\n });\n}\n\n/**\n * Validates the parameters of a request against a Superstruct schema.\n * Throws a JSON-RPC error if the parameters are invalid.\n *\n * @param value - The value to validate.\n * @param struct - The Superstruct schema to validate against.\n * @throws An error if the parameters are invalid.\n */\nexport function validateParams<ParamsType>(\n value: unknown | ParamsType,\n struct: Struct<ParamsType>,\n): asserts value is ParamsType {\n const [error] = validate(value, struct);\n\n if (error) {\n throw rpcErrors.invalidParams(\n formatValidationError(error, `Invalid params`),\n );\n }\n}\n\n/**\n * Checks if a string resembles an Ethereum address.\n *\n * @param str - The string to check.\n * @returns True if the string resembles an Ethereum address, false otherwise.\n */\nexport function resemblesAddress(str: string): boolean {\n // hex prefix 2 + 20 bytes\n return str.length === 2 + 20 * 2;\n}\n\n/**\n * Formats a Superstruct validation error into a human-readable string.\n *\n * @param error - The Superstruct validation error.\n * @param message - The base error message to prepend to the formatted details.\n * @returns The formatted error.\n */\nfunction formatValidationError(error: StructError, message: string): string {\n return `${message}\\n\\n${error\n .failures()\n .map(\n (failure) =>\n `${failure.path.join(' > ')}${failure.path.length ? ' - ' : ''}${failure.message}`,\n )\n .join('\\n')}`;\n}\n\nexport const DANGEROUS_PROTOTYPE_PROPERTIES = [\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n] as const;\n\n/**\n * Checks if a property name is dangerous for prototype pollution.\n *\n * @param key - The property name to check\n * @returns True if the property name is dangerous\n */\nfunction isDangerousProperty(key: string): boolean {\n return (DANGEROUS_PROTOTYPE_PROPERTIES as readonly string[]).includes(key);\n}\n\n/**\n * Recursively checks an object for dangerous prototype pollution properties.\n *\n * @param obj - The object to check\n * @throws rpcErrors.invalidInput() if a dangerous property is found\n */\nfunction checkObjectForPrototypePollution(obj: unknown): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n checkObjectForPrototypePollution(item);\n }\n return;\n }\n\n if (typeof obj === 'object') {\n for (const key of Object.getOwnPropertyNames(\n obj as Record<string, unknown>,\n )) {\n if (isDangerousProperty(key)) {\n throw rpcErrors.invalidInput();\n }\n checkObjectForPrototypePollution((obj as Record<string, unknown>)[key]);\n }\n }\n}\n\n/**\n * Validates V1 typed data (array format) for prototype pollution attacks.\n * V1 format: [{ type: 'string', name: 'fieldName', value: 'data' }, ...]\n *\n * @param data - The V1 typed data array to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataV1ForPrototypePollution(\n data: Record<string, unknown>[],\n): void {\n if (!data || !Array.isArray(data)) {\n return;\n }\n\n for (const item of data) {\n if (item && typeof item === 'object') {\n // Only check the 'value' field (the message data) for dangerous properties\n if (item.value !== null && typeof item.value === 'object') {\n checkObjectForPrototypePollution(item.value);\n }\n }\n }\n}\n\n/**\n * Validates V3/V4 typed data (EIP-712 format) for prototype pollution attacks.\n * Only checks the message field for dangerous properties.\n *\n * @param data - The stringified typed data to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataForPrototypePollution(data: string): void {\n const { message } = parseTypedMessage(data);\n\n // Check message recursively for dangerous properties\n if (message !== undefined) {\n checkObjectForPrototypePollution(message);\n }\n}\n\n/**\n * Validates that EIP-712 typed message data contains only keys defined in\n * the TYPED_MESSAGE_SCHEMA from `@metamask/eth-sig-util`. Rejects messages\n * with extraneous top-level keys.\n *\n * @param data - The stringified typed data to validate.\n * @throws rpcErrors.invalidInput() if extraneous keys are detected.\n */\nexport function validateTypedMessageKeys(data: string): void {\n const parsedData = parseTypedMessage(data);\n const allowedKeys = new Set(Object.keys(TYPED_MESSAGE_SCHEMA.properties));\n const hasExtraneousKey = Object.keys(parsedData).some(\n (key) => !allowedKeys.has(key),\n );\n\n if (hasExtraneousKey) {\n throw rpcErrors.invalidInput();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validation.cjs","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { TYPED_MESSAGE_SCHEMA } from '@metamask/eth-sig-util';\nimport { providerErrors, rpcErrors } from '@metamask/rpc-errors';\nimport type { Struct, StructError } from '@metamask/superstruct';\nimport { validate } from '@metamask/superstruct';\nimport type { Hex } from '@metamask/utils';\n\nimport type { WalletMiddlewareContext } from '../wallet';\nimport { parseTypedMessage } from './normalize';\n\n/**\n * Validates and normalizes a keyholder address for transaction- and\n * signature-related operations.\n *\n * @param address - The Ethereum address to validate and normalize.\n * @param context - The context of the request.\n * @param options - The options for the validation.\n * @param options.getAccounts - The function to get the accounts for the origin.\n * @returns The normalized address, if valid. Otherwise, throws\n * an error\n */\nexport async function validateAndNormalizeKeyholder(\n address: Hex,\n context: WalletMiddlewareContext,\n { getAccounts }: { getAccounts: (origin: string) => Promise<string[]> },\n): Promise<Hex> {\n if (\n typeof address === 'string' &&\n address.length > 0 &&\n resemblesAddress(address)\n ) {\n // Ensure that an \"unauthorized\" error is thrown if the requester\n // does not have the `eth_accounts` permission.\n const accounts = await getAccounts(context.assertGet('origin'));\n\n const normalizedAccounts: string[] = accounts.map((_address) =>\n _address.toLowerCase(),\n );\n\n const normalizedAddress = address.toLowerCase() as Hex;\n\n if (normalizedAccounts.includes(normalizedAddress)) {\n return normalizedAddress;\n }\n\n throw providerErrors.unauthorized();\n }\n\n throw rpcErrors.invalidParams({\n message: `Invalid parameters: must provide an Ethereum address.`,\n });\n}\n\n/**\n * Validates the parameters of a request against a Superstruct schema.\n * Throws a JSON-RPC error if the parameters are invalid.\n *\n * @param value - The value to validate.\n * @param struct - The Superstruct schema to validate against.\n * @throws An error if the parameters are invalid.\n */\nexport function validateParams<ParamsType>(\n value: unknown | ParamsType,\n struct: Struct<ParamsType>,\n): asserts value is ParamsType {\n const [error] = validate(value, struct);\n\n if (error) {\n throw rpcErrors.invalidParams(\n formatValidationError(error, `Invalid params`),\n );\n }\n}\n\n/**\n * Checks if a string resembles an Ethereum address.\n *\n * @param str - The string to check.\n * @returns True if the string resembles an Ethereum address, false otherwise.\n */\nexport function resemblesAddress(str: string): boolean {\n // hex prefix 2 + 20 bytes\n return str.length === 2 + 20 * 2;\n}\n\n/**\n * Formats a Superstruct validation error into a human-readable string.\n *\n * @param error - The Superstruct validation error.\n * @param message - The base error message to prepend to the formatted details.\n * @returns The formatted error.\n */\nfunction formatValidationError(error: StructError, message: string): string {\n return `${message}\\n\\n${error\n .failures()\n .map(\n (failure) =>\n `${failure.path.join(' > ')}${failure.path.length ? ' - ' : ''}${failure.message}`,\n )\n .join('\\n')}`;\n}\n\nexport const DANGEROUS_PROTOTYPE_PROPERTIES = [\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n] as const;\n\n/**\n * Checks if a property name is dangerous for prototype pollution.\n *\n * @param key - The property name to check\n * @returns True if the property name is dangerous\n */\nfunction isDangerousProperty(key: string): boolean {\n return (DANGEROUS_PROTOTYPE_PROPERTIES as readonly string[]).includes(key);\n}\n\n/**\n * Recursively checks an object for dangerous prototype pollution properties.\n *\n * @param obj - The object to check\n * @throws rpcErrors.invalidInput() if a dangerous property is found\n */\nfunction checkObjectForPrototypePollution(obj: unknown): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n checkObjectForPrototypePollution(item);\n }\n return;\n }\n\n if (typeof obj === 'object') {\n for (const key of Object.getOwnPropertyNames(\n obj as Record<string, unknown>,\n )) {\n if (isDangerousProperty(key)) {\n throw rpcErrors.invalidInput();\n }\n checkObjectForPrototypePollution((obj as Record<string, unknown>)[key]);\n }\n }\n}\n\n/**\n * Validates V1 typed data (array format) for prototype pollution attacks.\n * V1 format: [{ type: 'string', name: 'fieldName', value: 'data' }, ...]\n *\n * @param data - The V1 typed data array to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataV1ForPrototypePollution(\n data: Record<string, unknown>[],\n): void {\n if (!data || !Array.isArray(data)) {\n return;\n }\n\n for (const item of data) {\n if (item && typeof item === 'object') {\n // Only check the 'value' field (the message data) for dangerous properties\n if (item.value !== null && typeof item.value === 'object') {\n checkObjectForPrototypePollution(item.value);\n }\n }\n }\n}\n\n/**\n * Validates V3/V4 typed data (EIP-712 format) for prototype pollution attacks.\n * Only checks the message field for dangerous properties.\n *\n * @param data - The stringified typed data to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataForPrototypePollution(data: string): void {\n const { message } = parseTypedMessage(data);\n\n // Check message recursively for dangerous properties\n if (message !== undefined) {\n checkObjectForPrototypePollution(message);\n }\n}\n\n/**\n * Validates that EIP-712 typed message data contains only keys defined in\n * the TYPED_MESSAGE_SCHEMA from `@metamask/eth-sig-util`. Rejects messages\n * with extraneous top-level keys.\n *\n * @param data - The stringified typed data to validate.\n * @throws rpcErrors.invalidInput() if extraneous keys are detected.\n */\nexport function validateTypedMessageKeys(data: string): void {\n const parsedData = parseTypedMessage(data);\n const allowedKeys = new Set([\n ...Object.keys(TYPED_MESSAGE_SCHEMA.properties),\n 'metadata',\n ]);\n const hasExtraneousKey = Object.keys(parsedData).some(\n (key) => !allowedKeys.has(key),\n );\n\n if (hasExtraneousKey) {\n throw rpcErrors.invalidInput();\n }\n\n // Advanced Permissions adds `metadata: { justification: string, origin: string }` to eth_signTypedData requests.\n // see GatorPermissionsController.decodePermissionFromPermissionContextForOrigin for more details.\n const { metadata } = parsedData as { metadata?: unknown };\n if (metadata !== undefined) {\n if (typeof metadata !== 'object' || metadata === null) {\n throw rpcErrors.invalidInput();\n }\n\n const { justification, origin } = metadata as {\n justification?: unknown;\n origin?: unknown;\n };\n\n if (typeof justification !== 'string' || typeof origin !== 'string') {\n throw rpcErrors.invalidInput();\n }\n\n // we only need to check the keys length, because we already checked the known keys (justification and origin).\n if (Object.keys(metadata).length !== 2) {\n throw rpcErrors.invalidInput();\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.cts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,8BAA8B;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAE3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAkB;AAGzD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,uBAAuB,EAChC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACtE,OAAO,CAAC,GAAG,CAAC,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,EAC3B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GACzB,OAAO,CAAC,KAAK,IAAI,UAAU,CAQ7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAmBD,eAAO,MAAM,8BAA8B,oIAQjC,CAAC;AA0CX;;;;;;GAMG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,IAAI,CAaN;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOzE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validation.d.cts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,8BAA8B;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAE3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAkB;AAGzD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,uBAAuB,EAChC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACtE,OAAO,CAAC,GAAG,CAAC,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,EAC3B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GACzB,OAAO,CAAC,KAAK,IAAI,UAAU,CAQ7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAmBD,eAAO,MAAM,8BAA8B,oIAQjC,CAAC;AA0CX;;;;;;GAMG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,IAAI,CAaN;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOzE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAoC3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.mts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,8BAA8B;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAE3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAkB;AAGzD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,uBAAuB,EAChC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACtE,OAAO,CAAC,GAAG,CAAC,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,EAC3B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GACzB,OAAO,CAAC,KAAK,IAAI,UAAU,CAQ7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAmBD,eAAO,MAAM,8BAA8B,oIAQjC,CAAC;AA0CX;;;;;;GAMG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,IAAI,CAaN;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOzE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validation.d.mts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAe,8BAA8B;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,wBAAwB;AAE3C,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAkB;AAGzD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,uBAAuB,EAChC,EAAE,WAAW,EAAE,EAAE;IAAE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,GACtE,OAAO,CAAC,GAAG,CAAC,CA0Bd;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,UAAU,EACvC,KAAK,EAAE,OAAO,GAAG,UAAU,EAC3B,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,GACzB,OAAO,CAAC,KAAK,IAAI,UAAU,CAQ7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGrD;AAmBD,eAAO,MAAM,8BAA8B,oIAQjC,CAAC;AA0CX;;;;;;GAMG;AACH,wBAAgB,wCAAwC,CACtD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,IAAI,CAaN;AAED;;;;;;GAMG;AACH,wBAAgB,sCAAsC,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAOzE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAoC3D"}
|
|
@@ -155,10 +155,29 @@ export function validateTypedDataForPrototypePollution(data) {
|
|
|
155
155
|
*/
|
|
156
156
|
export function validateTypedMessageKeys(data) {
|
|
157
157
|
const parsedData = parseTypedMessage(data);
|
|
158
|
-
const allowedKeys = new Set(
|
|
158
|
+
const allowedKeys = new Set([
|
|
159
|
+
...Object.keys(TYPED_MESSAGE_SCHEMA.properties),
|
|
160
|
+
'metadata',
|
|
161
|
+
]);
|
|
159
162
|
const hasExtraneousKey = Object.keys(parsedData).some((key) => !allowedKeys.has(key));
|
|
160
163
|
if (hasExtraneousKey) {
|
|
161
164
|
throw rpcErrors.invalidInput();
|
|
162
165
|
}
|
|
166
|
+
// Advanced Permissions adds `metadata: { justification: string, origin: string }` to eth_signTypedData requests.
|
|
167
|
+
// see GatorPermissionsController.decodePermissionFromPermissionContextForOrigin for more details.
|
|
168
|
+
const { metadata } = parsedData;
|
|
169
|
+
if (metadata !== undefined) {
|
|
170
|
+
if (typeof metadata !== 'object' || metadata === null) {
|
|
171
|
+
throw rpcErrors.invalidInput();
|
|
172
|
+
}
|
|
173
|
+
const { justification, origin } = metadata;
|
|
174
|
+
if (typeof justification !== 'string' || typeof origin !== 'string') {
|
|
175
|
+
throw rpcErrors.invalidInput();
|
|
176
|
+
}
|
|
177
|
+
// we only need to check the keys length, because we already checked the known keys (justification and origin).
|
|
178
|
+
if (Object.keys(metadata).length !== 2) {
|
|
179
|
+
throw rpcErrors.invalidInput();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
163
182
|
}
|
|
164
183
|
//# sourceMappingURL=validation.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.mjs","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { TYPED_MESSAGE_SCHEMA } from '@metamask/eth-sig-util';\nimport { providerErrors, rpcErrors } from '@metamask/rpc-errors';\nimport type { Struct, StructError } from '@metamask/superstruct';\nimport { validate } from '@metamask/superstruct';\nimport type { Hex } from '@metamask/utils';\n\nimport type { WalletMiddlewareContext } from '../wallet';\nimport { parseTypedMessage } from './normalize';\n\n/**\n * Validates and normalizes a keyholder address for transaction- and\n * signature-related operations.\n *\n * @param address - The Ethereum address to validate and normalize.\n * @param context - The context of the request.\n * @param options - The options for the validation.\n * @param options.getAccounts - The function to get the accounts for the origin.\n * @returns The normalized address, if valid. Otherwise, throws\n * an error\n */\nexport async function validateAndNormalizeKeyholder(\n address: Hex,\n context: WalletMiddlewareContext,\n { getAccounts }: { getAccounts: (origin: string) => Promise<string[]> },\n): Promise<Hex> {\n if (\n typeof address === 'string' &&\n address.length > 0 &&\n resemblesAddress(address)\n ) {\n // Ensure that an \"unauthorized\" error is thrown if the requester\n // does not have the `eth_accounts` permission.\n const accounts = await getAccounts(context.assertGet('origin'));\n\n const normalizedAccounts: string[] = accounts.map((_address) =>\n _address.toLowerCase(),\n );\n\n const normalizedAddress = address.toLowerCase() as Hex;\n\n if (normalizedAccounts.includes(normalizedAddress)) {\n return normalizedAddress;\n }\n\n throw providerErrors.unauthorized();\n }\n\n throw rpcErrors.invalidParams({\n message: `Invalid parameters: must provide an Ethereum address.`,\n });\n}\n\n/**\n * Validates the parameters of a request against a Superstruct schema.\n * Throws a JSON-RPC error if the parameters are invalid.\n *\n * @param value - The value to validate.\n * @param struct - The Superstruct schema to validate against.\n * @throws An error if the parameters are invalid.\n */\nexport function validateParams<ParamsType>(\n value: unknown | ParamsType,\n struct: Struct<ParamsType>,\n): asserts value is ParamsType {\n const [error] = validate(value, struct);\n\n if (error) {\n throw rpcErrors.invalidParams(\n formatValidationError(error, `Invalid params`),\n );\n }\n}\n\n/**\n * Checks if a string resembles an Ethereum address.\n *\n * @param str - The string to check.\n * @returns True if the string resembles an Ethereum address, false otherwise.\n */\nexport function resemblesAddress(str: string): boolean {\n // hex prefix 2 + 20 bytes\n return str.length === 2 + 20 * 2;\n}\n\n/**\n * Formats a Superstruct validation error into a human-readable string.\n *\n * @param error - The Superstruct validation error.\n * @param message - The base error message to prepend to the formatted details.\n * @returns The formatted error.\n */\nfunction formatValidationError(error: StructError, message: string): string {\n return `${message}\\n\\n${error\n .failures()\n .map(\n (failure) =>\n `${failure.path.join(' > ')}${failure.path.length ? ' - ' : ''}${failure.message}`,\n )\n .join('\\n')}`;\n}\n\nexport const DANGEROUS_PROTOTYPE_PROPERTIES = [\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n] as const;\n\n/**\n * Checks if a property name is dangerous for prototype pollution.\n *\n * @param key - The property name to check\n * @returns True if the property name is dangerous\n */\nfunction isDangerousProperty(key: string): boolean {\n return (DANGEROUS_PROTOTYPE_PROPERTIES as readonly string[]).includes(key);\n}\n\n/**\n * Recursively checks an object for dangerous prototype pollution properties.\n *\n * @param obj - The object to check\n * @throws rpcErrors.invalidInput() if a dangerous property is found\n */\nfunction checkObjectForPrototypePollution(obj: unknown): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n checkObjectForPrototypePollution(item);\n }\n return;\n }\n\n if (typeof obj === 'object') {\n for (const key of Object.getOwnPropertyNames(\n obj as Record<string, unknown>,\n )) {\n if (isDangerousProperty(key)) {\n throw rpcErrors.invalidInput();\n }\n checkObjectForPrototypePollution((obj as Record<string, unknown>)[key]);\n }\n }\n}\n\n/**\n * Validates V1 typed data (array format) for prototype pollution attacks.\n * V1 format: [{ type: 'string', name: 'fieldName', value: 'data' }, ...]\n *\n * @param data - The V1 typed data array to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataV1ForPrototypePollution(\n data: Record<string, unknown>[],\n): void {\n if (!data || !Array.isArray(data)) {\n return;\n }\n\n for (const item of data) {\n if (item && typeof item === 'object') {\n // Only check the 'value' field (the message data) for dangerous properties\n if (item.value !== null && typeof item.value === 'object') {\n checkObjectForPrototypePollution(item.value);\n }\n }\n }\n}\n\n/**\n * Validates V3/V4 typed data (EIP-712 format) for prototype pollution attacks.\n * Only checks the message field for dangerous properties.\n *\n * @param data - The stringified typed data to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataForPrototypePollution(data: string): void {\n const { message } = parseTypedMessage(data);\n\n // Check message recursively for dangerous properties\n if (message !== undefined) {\n checkObjectForPrototypePollution(message);\n }\n}\n\n/**\n * Validates that EIP-712 typed message data contains only keys defined in\n * the TYPED_MESSAGE_SCHEMA from `@metamask/eth-sig-util`. Rejects messages\n * with extraneous top-level keys.\n *\n * @param data - The stringified typed data to validate.\n * @throws rpcErrors.invalidInput() if extraneous keys are detected.\n */\nexport function validateTypedMessageKeys(data: string): void {\n const parsedData = parseTypedMessage(data);\n const allowedKeys = new Set(Object.keys(TYPED_MESSAGE_SCHEMA.properties));\n const hasExtraneousKey = Object.keys(parsedData).some(\n (key) => !allowedKeys.has(key),\n );\n\n if (hasExtraneousKey) {\n throw rpcErrors.invalidInput();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validation.mjs","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { TYPED_MESSAGE_SCHEMA } from '@metamask/eth-sig-util';\nimport { providerErrors, rpcErrors } from '@metamask/rpc-errors';\nimport type { Struct, StructError } from '@metamask/superstruct';\nimport { validate } from '@metamask/superstruct';\nimport type { Hex } from '@metamask/utils';\n\nimport type { WalletMiddlewareContext } from '../wallet';\nimport { parseTypedMessage } from './normalize';\n\n/**\n * Validates and normalizes a keyholder address for transaction- and\n * signature-related operations.\n *\n * @param address - The Ethereum address to validate and normalize.\n * @param context - The context of the request.\n * @param options - The options for the validation.\n * @param options.getAccounts - The function to get the accounts for the origin.\n * @returns The normalized address, if valid. Otherwise, throws\n * an error\n */\nexport async function validateAndNormalizeKeyholder(\n address: Hex,\n context: WalletMiddlewareContext,\n { getAccounts }: { getAccounts: (origin: string) => Promise<string[]> },\n): Promise<Hex> {\n if (\n typeof address === 'string' &&\n address.length > 0 &&\n resemblesAddress(address)\n ) {\n // Ensure that an \"unauthorized\" error is thrown if the requester\n // does not have the `eth_accounts` permission.\n const accounts = await getAccounts(context.assertGet('origin'));\n\n const normalizedAccounts: string[] = accounts.map((_address) =>\n _address.toLowerCase(),\n );\n\n const normalizedAddress = address.toLowerCase() as Hex;\n\n if (normalizedAccounts.includes(normalizedAddress)) {\n return normalizedAddress;\n }\n\n throw providerErrors.unauthorized();\n }\n\n throw rpcErrors.invalidParams({\n message: `Invalid parameters: must provide an Ethereum address.`,\n });\n}\n\n/**\n * Validates the parameters of a request against a Superstruct schema.\n * Throws a JSON-RPC error if the parameters are invalid.\n *\n * @param value - The value to validate.\n * @param struct - The Superstruct schema to validate against.\n * @throws An error if the parameters are invalid.\n */\nexport function validateParams<ParamsType>(\n value: unknown | ParamsType,\n struct: Struct<ParamsType>,\n): asserts value is ParamsType {\n const [error] = validate(value, struct);\n\n if (error) {\n throw rpcErrors.invalidParams(\n formatValidationError(error, `Invalid params`),\n );\n }\n}\n\n/**\n * Checks if a string resembles an Ethereum address.\n *\n * @param str - The string to check.\n * @returns True if the string resembles an Ethereum address, false otherwise.\n */\nexport function resemblesAddress(str: string): boolean {\n // hex prefix 2 + 20 bytes\n return str.length === 2 + 20 * 2;\n}\n\n/**\n * Formats a Superstruct validation error into a human-readable string.\n *\n * @param error - The Superstruct validation error.\n * @param message - The base error message to prepend to the formatted details.\n * @returns The formatted error.\n */\nfunction formatValidationError(error: StructError, message: string): string {\n return `${message}\\n\\n${error\n .failures()\n .map(\n (failure) =>\n `${failure.path.join(' > ')}${failure.path.length ? ' - ' : ''}${failure.message}`,\n )\n .join('\\n')}`;\n}\n\nexport const DANGEROUS_PROTOTYPE_PROPERTIES = [\n '__proto__',\n 'constructor',\n 'prototype',\n '__defineGetter__',\n '__defineSetter__',\n '__lookupGetter__',\n '__lookupSetter__',\n] as const;\n\n/**\n * Checks if a property name is dangerous for prototype pollution.\n *\n * @param key - The property name to check\n * @returns True if the property name is dangerous\n */\nfunction isDangerousProperty(key: string): boolean {\n return (DANGEROUS_PROTOTYPE_PROPERTIES as readonly string[]).includes(key);\n}\n\n/**\n * Recursively checks an object for dangerous prototype pollution properties.\n *\n * @param obj - The object to check\n * @throws rpcErrors.invalidInput() if a dangerous property is found\n */\nfunction checkObjectForPrototypePollution(obj: unknown): void {\n if (obj === null || obj === undefined) {\n return;\n }\n\n if (Array.isArray(obj)) {\n for (const item of obj) {\n checkObjectForPrototypePollution(item);\n }\n return;\n }\n\n if (typeof obj === 'object') {\n for (const key of Object.getOwnPropertyNames(\n obj as Record<string, unknown>,\n )) {\n if (isDangerousProperty(key)) {\n throw rpcErrors.invalidInput();\n }\n checkObjectForPrototypePollution((obj as Record<string, unknown>)[key]);\n }\n }\n}\n\n/**\n * Validates V1 typed data (array format) for prototype pollution attacks.\n * V1 format: [{ type: 'string', name: 'fieldName', value: 'data' }, ...]\n *\n * @param data - The V1 typed data array to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataV1ForPrototypePollution(\n data: Record<string, unknown>[],\n): void {\n if (!data || !Array.isArray(data)) {\n return;\n }\n\n for (const item of data) {\n if (item && typeof item === 'object') {\n // Only check the 'value' field (the message data) for dangerous properties\n if (item.value !== null && typeof item.value === 'object') {\n checkObjectForPrototypePollution(item.value);\n }\n }\n }\n}\n\n/**\n * Validates V3/V4 typed data (EIP-712 format) for prototype pollution attacks.\n * Only checks the message field for dangerous properties.\n *\n * @param data - The stringified typed data to validate\n * @throws rpcErrors.invalidInput() if prototype pollution is detected\n */\nexport function validateTypedDataForPrototypePollution(data: string): void {\n const { message } = parseTypedMessage(data);\n\n // Check message recursively for dangerous properties\n if (message !== undefined) {\n checkObjectForPrototypePollution(message);\n }\n}\n\n/**\n * Validates that EIP-712 typed message data contains only keys defined in\n * the TYPED_MESSAGE_SCHEMA from `@metamask/eth-sig-util`. Rejects messages\n * with extraneous top-level keys.\n *\n * @param data - The stringified typed data to validate.\n * @throws rpcErrors.invalidInput() if extraneous keys are detected.\n */\nexport function validateTypedMessageKeys(data: string): void {\n const parsedData = parseTypedMessage(data);\n const allowedKeys = new Set([\n ...Object.keys(TYPED_MESSAGE_SCHEMA.properties),\n 'metadata',\n ]);\n const hasExtraneousKey = Object.keys(parsedData).some(\n (key) => !allowedKeys.has(key),\n );\n\n if (hasExtraneousKey) {\n throw rpcErrors.invalidInput();\n }\n\n // Advanced Permissions adds `metadata: { justification: string, origin: string }` to eth_signTypedData requests.\n // see GatorPermissionsController.decodePermissionFromPermissionContextForOrigin for more details.\n const { metadata } = parsedData as { metadata?: unknown };\n if (metadata !== undefined) {\n if (typeof metadata !== 'object' || metadata === null) {\n throw rpcErrors.invalidInput();\n }\n\n const { justification, origin } = metadata as {\n justification?: unknown;\n origin?: unknown;\n };\n\n if (typeof justification !== 'string' || typeof origin !== 'string') {\n throw rpcErrors.invalidInput();\n }\n\n // we only need to check the keys length, because we already checked the known keys (justification and origin).\n if (Object.keys(metadata).length !== 2) {\n throw rpcErrors.invalidInput();\n }\n }\n}\n"]}
|