Account management Snap security guidelines
Refer to the following security guidelines when creating an account management Snap.
Do not add secret information to account objects
Ensure that you do not store any secret information in account objects, since they are exposed to dapps and MetaMask. For example:
-
❌ Do NOT do this:
const account: KeyringAccount = {
id: uuid(),
options: {
privateKey: "0x01234...78", // !!! DO NOT DO THIS !!!
},
address,
methods: [
EthMethod.PersonalSign,
EthMethod.Sign,
EthMethod.SignTransaction,
EthMethod.SignTypedDataV1,
EthMethod.SignTypedDataV3,
EthMethod.SignTypedDataV4,
],
type: EthAccountType.Eoa,
} -
✅ Do this instead:
Store any secret information that you need in the Snap's state:
await snap.request({
method: "snap_manageState",
params: {
operation: "update",
newState: {
// Your Snap's state here.
privateKey: "0x01234...78",
},
},
})
Limit the methods exposed to dapps
By default, MetaMask enforces the following restrictions on calling Account Management API methods on your Snap based on the caller origin:
For example, a dapp is not allowed to call the keyring_submitRequest
method of your Snap, and
MetaMask is not allowed to call the keyring_createAccount
method of your Snap.
MetaMask also enforces that only dapps allowlisted in the Snap's manifest file using the
endowment:keyring
permission can call these methods.
We recommend further restricting the methods exposed to dapps according to your Snap's functionality.
For example, if your Snap does not support account deletion via dapps, your Snap should reject
calls to the keyring_deleteAccount
method originating from dapps.
Your Snap can also impose varying restrictions depending on the calling dapp. For example, Dapp 1 may have access to a different set of methods than Dapp 2. The following is an example of implementing such logic:
const permissions: Record<string, string[]> = {
"https://<Dapp 1 domain>": [
// List of allowed methods for Dapp 1.
],
"https://<Dapp 2 domain>": [
// List of allowed methods for Dapp 2.
],
}
if (origin !== "metamask" && !permissions[origin]?.includes(request.method)) {
// Reject the request.
}
Both dapps must be allowlisted in the Snap's manifest file.
Ensure the redirect URL cannot be manipulated
If your Snap implements an asynchronous transaction flow, ensure that the redirect URL is valid and cannot be manipulated, otherwise the user can be redirected to a malicious website.
async submitRequest(request: KeyringRequest): Promise<SubmitRequestResponse> {
// Your Snap's custom logic.
return {
pending: true,
redirect: {
message: "Please continue in the dapp.",
url: "https://<dapp domain>/sign?tx=1234", // !!! ENSURE THIS IS A SAFE URL !!!
},
};
}
Only HTTPS URLs are allowed in the url
field, and the provided URL is checked against a
list of blocked domains.
However, for development purposes, HTTP URLs are allowed on Flask.
MetaMask also requires the redirect URL to be within a site allowlisted in the Snap's manifest file.
Remove all debug code from your production Snap
Ensure that all debug code is removed from your production Snap. Exposing debug code can lead to multiple security vulnerabilities. For example, secret information can be logged to the console, or a malicious dapp can bypass a security check.
Remove sensitive information from errors
Ensure that all error messages returned by your Snap are sanitized. Failing to sanitize error messages can lead to exposing secrets or other sensitive information to dapps or to MetaMask.
For example:
-
❌ Do NOT do this:
// If inputSecretValue contains invalid hexadecimal characters, its value
// will be added to the error thrown by toBuffer.
const privateKey = toBuffer(inputSecretValue)
// Use privateKey here. -
✅ Do this instead:
try {
const privateKey = toBuffer(inputSecretValue)
// Use privateKey here.
} catch (error) {
throw new Error("Invalid private key")
}
Expose Account Management API methods using the onKeyringRequest
entry point
The onRpcRequest
entry point is a general-purpose
entry point and has no restrictions on the methods that can be called.
Ensure that you only expose Account Management API methods using the
onKeyringRequest
entry point, which has extra
security checks.
For example:
-
❌ Do NOT do this:
export const onRpcRequest: OnRpcRequestHandler = async ({
// ~~~ ~~~
origin,
request,
}) => {
return handleKeyringRequest(keyring, request)
} -
✅ Do this instead:
export const onKeyringRequest: OnKeyringRequestHandler = async ({
// ~~~~~~~ ~~~~~~~
origin,
request,
}) => {
// Any custom logic or extra security checks here.
return handleKeyringRequest(keyring, request)
}
Do not fetch remote code from inside your Snap
Ensure that your Snap is self-contained and does not fetch code from external sources. Otherwise, a compromised server can use this mechanism to inject malicious code into your Snap.