ERC-7484 Vulnerability Patterns
ERC-7484 (Registry Extension for ERC-7579) defines a standard way for modular smart accounts to verify modules through a trusted registry before installation or use. It ensures that only attested (verified) modules can be installed, preventing malicious or unaudited code from being added to a user’s smart account.
This article summarizes ERC-7484 vulnerability patterns with real audit cases to help you understand the security implications.
ERC-7484 Core Concepts
What is ERC-7484?
┌─────────────────────────────────────────────────────────────────┐
│ Modular Smart Account Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Smart Account (e.g., Nexus, Safe): │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Core Account │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ • Holds user's assets │ │ │
│ │ │ • Manages module installation/uninstallation │ │ │
│ │ │ • Delegates execution to installed modules │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Installed Modules: │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Validator │ │ Executor │ │ Hook │ │ │
│ │ │ (signs tx) │ │ (actions) │ │ (pre/post) │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Problem: How to ensure modules are safe? │
│ → Anyone could deploy a malicious module │
│ → User might install a backdoored validator │
│ → Attacker could drain the account │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ERC-7484: Registry Solution │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Registry Contract: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Trusted Attesters (security auditors, protocol team): │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Attester1│ │Attester2│ │Attester3│ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │ │
│ │ └───────────┼───────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Module Attestations: │ │ │
│ │ │ • Module 0xAAA: attested by [1,2,3] ✓ │ │ │
│ │ │ • Module 0xBBB: attested by [1,2] ✓ │ │ │
│ │ │ • Module 0xCCC: attested by [1] (insufficient) │ │ │
│ │ │ • Module 0xDDD: no attestations ✗ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ registry.check(module, moduleType): │ │
│ │ → Reverts if module doesn't have enough attestations │ │
│ │ → User configures which attesters to trust │ │
│ │ → User sets threshold (e.g., need 2 of 3 attesters) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ERC-7484 Requirement: │
│ "The Registry MUST be queried about module A at least once │
│ before or during the transaction in which A is called for │
│ the first time." │
│ │
│ This includes the onInstall() call during module installation! │
│ │
└─────────────────────────────────────────────────────────────────┘
Vulnerability Patterns
1. Registry Check Bypass in Bootstrap Initialization
Prerequisite Knowledge:
ERC-7484 specifies that “The Registry MUST be queried about module A at least once before or during the transaction in which A is called for the first time.” This includes the onInstall() call during initialization. The check must happen BEFORE the module’s first use.
Root Cause:
The Bootstrap contract installs modules (calling their onInstall() functions) BEFORE configuring the registry address. During module installation, the _checkRegistry function skips the check because registry is still address(0). This violates ERC-7484.
Attack Vector:
The attacker deploys a malicious validator module that is NOT attested in the registry. When a user creates a new Nexus account through the Factory, the Factory calls Bootstrap.initNexus() via delegatecall. Due to the wrong order of operations, _installValidator() is called before _configureRegistry(). At this point, the registry address is still address(0), so _checkRegistry() silently skips the attestation check. The malicious validator’s onInstall() executes without verification. Once installed, the attacker can use this validator to sign arbitrary transactions and drain the account.
Source:
[codehawks/2024-07-biconomy] Registry is never called when setting up modules using the Bootstrap contract (
H-02.md)Business Scenario:
Biconomy Nexus - Modular Smart Account (ERC-7579 & ERC-4337 compliant) ┌────────────────────────────────────────────────────────────────┐ │ │ │ What is Nexus? │ │ A modular smart account that allows users to install │ │ different modules (validators, executors, hooks) to │ │ customize their account's behavior. │ │ │ │ Key Terms: │ │ • Validator: Module that validates UserOp signatures, │ │ decides if a transaction is authorized │ │ • Executor: Module that can execute arbitrary actions │ │ on behalf of the account (batch calls, etc.) │ │ • Hook: Module that runs before/after other module calls, │ │ can enforce policies or log activity │ │ • Fallback Handler: Handles calls to undefined functions │ │ • Registry: ERC-7484 contract that verifies module safety │ │ • Attester: Trusted entity (auditor, protocol team) that │ │ vouches for module safety by signing attestations │ │ • Threshold: Minimum number of attesters required │ │ • Bootstrap: Contract used to initialize a new smart account │ │ via delegatecall during account creation │ │ │ │ How Bootstrap Works: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ │ │ │ │ Factory Bootstrap │ │ │ │ │ │ │ │ │ │ │ 1. create2(Nexus) │ │ │ │ │ │────────────────────> │ │ │ │ │ │ │ │ │ │ │ │ 2. Nexus.delegatecall( │ │ │ │ │ │ Bootstrap.initNexus() │ │ │ │ │ │ ) │ │ │ │ │ │─────────────────────────────>│ │ │ │ │ │ │ │ │ │ │ │ Bootstrap code runs in │ │ │ │ │ │ Nexus's storage context │ │ │ │ │ │ (this is important!) │ │ │ │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ Attack Flow: ┌────────────────────────────────────────────────────────────────┐ │ │ │ Scenario: User installs an unaudited module, Registry │ │ protection fails to prevent it. │ │ │ │ Step 1: User selects a validator module │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ // User finds a validator module (e.g., from a forum, │ │ │ │ // third-party developer, or malicious recommendation) │ │ │ │ // This module is NOT attested in the registry │ │ │ │ │ │ │ │ contract UnsafeValidator is IValidator { │ │ │ │ function validateUserOp(...) external returns (...) │ │ │ │ // Contains a backdoor or vulnerability │ │ │ │ } │ │ │ │ } │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ Step 2: User creates account with this module │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ // User calls Factory to create their Nexus account │ │ │ │ // They configure a legitimate registry with attesters │ │ │ │ // EXPECTING the registry to reject unattested modules │ │ │ │ │ │ │ │ Bootstrap.initNexus( │ │ │ │ validators: [unsafeValidator], // Not attested! │ │ │ │ registry: legitimateRegistry, │ │ │ │ attesters: [attester1, attester2], │ │ │ │ threshold: 2 │ │ │ │ ) │ │ │ │ │ │ │ │ // EXPECTED: Registry check fails, module rejected │ │ │ │ // ACTUAL: Registry not configured yet, check skipped! │ │ │ │ // Result: Unattested module installed successfully │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ Step 3: User loses Registry protection │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ // The unsafe validator is now installed │ │ │ │ // User believes they are protected by the registry │ │ │ │ // But the attestation check was never performed │ │ │ │ // If the module has a backdoor, funds can be drained │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────────┘ Mitigation: ┌────────────────────────────────────────────────────────────────┐ │ │ │ Configure registry BEFORE installing any modules: │ │ │ │ function initNexus(...) external { │ │ // FIRST: Configure registry │ │ _configureRegistry(registry, attesters, threshold); │ │ │ │ // THEN: Install modules (registry checks will run) │ │ for (uint256 i = 0; i < validators.length; i++) { │ │ _installValidator(validators[i].module, ...); │ │ } │ │ // ... install other modules │ │ } │ │ │ └────────────────────────────────────────────────────────────────┘contracts/utils/RegistryBootstrap.solfunction initNexusWithSingleValidator( IModule validator, bytes calldata data, IERC7484 registry, address[] calldata attesters, uint8 threshold ) external { _installValidator(address(validator), data); // VULNERABILITY: Installing validator before configuring registry _configureRegistry(registry, attesters, threshold); }contracts/base/ModuleManager.sol// _installValidator uses the withRegistry modifier to check the registry function _installValidator(address validator, bytes calldata data) internal virtual withRegistry(validator, MODULE_TYPE_VALIDATOR) // Registry check here { if (!IValidator(validator).isModuleType(MODULE_TYPE_VALIDATOR)) revert MismatchModuleTypeId(); if (validator == _DEFAULT_VALIDATOR) { revert DefaultValidatorAlreadyInstalled(); } _getAccountStorage().validators.push(validator); // Store in account registry IValidator(validator).onInstall(data); // Call module's onInstall }contracts/base/RegistryAdapter.sol// The withRegistry modifier calls _checkRegistry before function execution modifier withRegistry(address module, uint256 moduleType) { _checkRegistry(module, moduleType); // Check runs BEFORE function body _; } function _checkRegistry(address module, uint256 moduleType) internal view { IERC7484 moduleRegistry = registry; if (address(moduleRegistry) != address(0)) { // This will revert if attestations or threshold are not met moduleRegistry.check(module, moduleType); } // VULNERABILITY: Check bypassed when registry is not yet configured (address(0)) }
ERC-7484 Audit Checklist
High-Risk Scenarios: Smart account initialization, module installation, bootstrap contracts, factory contracts.
| # | Check Item | Related Pattern |
|---|---|---|
| ✅ | Is the registry configured BEFORE any module installation? Modules installed before registry configuration bypass the check | Pattern 1 |
| ✅ | Can users initialize accounts with arbitrary modules? If initialization doesn’t check registry, malicious modules can be installed | Pattern 1 |
InfiniteSec