ERC-7484 Vulnerability Patterns

December 23, 2025

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.sol

      function 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