Issue: setAddressFrozen(address(0)) should be rejected
Affects: CMTAT EnforcementModule / EnforcementModuleInternal
Severity: Informational
Reporter: Nethermind Audit Agent on CMTAT FHE
Summary
EnforcementModule.setAddressFrozen accepts address(0) without restriction.
Freezing the null address has no legitimate use case — address(0) holds no tokens
and has no private key — but it can be exploited to impose a global transfer block
that bypasses role separation between ENFORCER_ROLE and PAUSER_ROLE.
Affected Functions
EnforcementModule.setAddressFrozen(address account, bool freeze)
EnforcementModule.setAddressFrozen(address account, bool freeze, bytes calldata data)
EnforcementModule.batchSetAddressFrozen(address[] calldata accounts, bool[] calldata freezes)
All three delegate to EnforcementModuleInternal._addAddressToTheList, which writes
directly to the frozen mapping without any zero-address check.
Attack Vector
Many CMTAT integrations pass address(0) as a synthetic spender in transfer
validation paths. For example, a confidential token layer may call:
_canTransferisFrozen(address(0), from, to)
Because _canTransferisFrozen checks isFrozen(spender) || isFrozen(from) || isFrozen(to),
freezing address(0) makes the spender check always return true, blocking every
direct holder transfer without holding PAUSER_ROLE.
This lets a holder of ENFORCER_ROLE (intended only to freeze individual accounts)
effectively pause all transfers — a privilege they are explicitly not supposed to have.
Suggested Fix
Add a zero-address guard in EnforcementModuleInternal._addAddressToTheList (the
single internal entry point for all three public functions):
function _addAddressToTheList(
EnforcementModuleInternalStorage storage $,
address account,
bool status,
bytes memory data
) internal virtual {
require(account != address(0), "EnforcementModule: zero address");
$._list[account] = status;
}
Or, if a custom error is preferred (recommended for gas and clarity):
error EnforcementModule_ZeroAddress();
function _addAddressToTheList(...) internal virtual {
if (account == address(0)) revert EnforcementModule_ZeroAddress();
$._list[account] = status;
}
Placing the guard at the internal level covers all three public functions and any
future callers of _addAddressToTheList automatically.
References
CMTAT/contracts/modules/internal/EnforcementModuleInternal.sol — _addAddressToTheList
CMTAT/contracts/modules/wrapper/core/EnforcementModule.sol — setAddressFrozen, batchSetAddressFrozen
contracts/CMTATConfidentialBase.sol — workaround applied (spender changed to from)
- Nethermind AuditAgent Finding 3 —
doc/audit/nethermind-audit-agent/nethermind-audit-agent-report-feedback.md
Issue:
setAddressFrozen(address(0))should be rejectedAffects: CMTAT
EnforcementModule/EnforcementModuleInternalSeverity: Informational
Reporter: Nethermind Audit Agent on CMTAT FHE
Summary
EnforcementModule.setAddressFrozenacceptsaddress(0)without restriction.Freezing the null address has no legitimate use case —
address(0)holds no tokensand has no private key — but it can be exploited to impose a global transfer block
that bypasses role separation between
ENFORCER_ROLEandPAUSER_ROLE.Affected Functions
All three delegate to
EnforcementModuleInternal._addAddressToTheList, which writesdirectly to the frozen mapping without any zero-address check.
Attack Vector
Many CMTAT integrations pass
address(0)as a synthetic spender in transfervalidation paths. For example, a confidential token layer may call:
Because
_canTransferisFrozenchecksisFrozen(spender) || isFrozen(from) || isFrozen(to),freezing
address(0)makes the spender check always returntrue, blocking everydirect holder transfer without holding
PAUSER_ROLE.This lets a holder of
ENFORCER_ROLE(intended only to freeze individual accounts)effectively pause all transfers — a privilege they are explicitly not supposed to have.
Suggested Fix
Add a zero-address guard in
EnforcementModuleInternal._addAddressToTheList(thesingle internal entry point for all three public functions):
Or, if a custom error is preferred (recommended for gas and clarity):
Placing the guard at the internal level covers all three public functions and any
future callers of
_addAddressToTheListautomatically.References
CMTAT/contracts/modules/internal/EnforcementModuleInternal.sol—_addAddressToTheListCMTAT/contracts/modules/wrapper/core/EnforcementModule.sol—setAddressFrozen,batchSetAddressFrozencontracts/CMTATConfidentialBase.sol— workaround applied (spender changed tofrom)doc/audit/nethermind-audit-agent/nethermind-audit-agent-report-feedback.md