7Block Labs
Decentralized Finance

ByAUJay

Summary: Token-balance pings don’t keep a DAO treasury safe. The single on‑chain signal you must escalate immediately is ChangedFallbackHandler on your Safe (formerly Gnosis Safe) multisig. This post explains why, shows how to monitor it precisely across chains, and gives a runnable incident playbook and allowlist for legitimate handler changes.

DAO Treasury Multisig: From “DAO Treasury Multisig – Token Balance Changed” to “ChangedFallbackHandler”

Most treasuries still treat “Token Balance Changed” alerts as the main health indicator. That’s noise. The structurally dangerous change on a Safe-based treasury is a configuration mutation—especially a fallback handler rotation. Safe emits an on-chain event called ChangedFallbackHandler whenever the contract that handles unknown function calls and token callbacks for your Safe is replaced. Treat that as P0. Everything else—individual transfers, dust, spam—should be downstream of strong config monitoring. (docs.safe.global)

Below, we explain what the fallback handler does, why an attacker wants to change it, which changes are legitimate, and how to monitor and respond within minutes using logs, the Safe Transaction/Event Services, and security networks like Forta. We include concrete code, addresses, and a severity matrix you can roll out this week.


Why “Token Balance Changed” is the wrong north star

  • Balance movements are high-volume, low-signal. Dusting, airdrops, NFT spam, and MEV swaps will constantly flip balances without threatening admin control of the treasury. Even block explorers now hide zero-value token transfers by default to reduce spam and “address poisoning” noise. If your alerting still treats these as P1, you’ll burn your on-call team. (coindesk.com)
  • Balance diffs are effects, not causes. By the time your balance alert fires, the permission change that enabled the drain may be 1–N blocks old. Structural changes—owners/thresholds, guards, modules, and the fallback handler—are the causes. Monitor them first. Safe’s emitted events are explicit and stable: AddedOwner, RemovedOwner, ChangedThreshold, EnabledModule, DisabledModule, ChangedGuard, and ChangedFallbackHandler. (docs.safe.global)

The fallback handler, in 60 seconds

A Safe can forward any unrecognized function calls to a pluggable “fallback handler” contract. The handler enables features the core Safe doesn’t implement directly: token receive hooks (ERC‑721/1155/777), ERC‑1271 contract signature validation, simulation helpers, or ERC‑4337 entrypoint validation. Changing the handler is a first-class Safe transaction that emits ChangedFallbackHandler. (help.safe.global)

How it works, precisely:

  • The Safe’s FallbackManager stores the handler address and forwards unknown calls to it, appending the original caller to calldata for context. Security constraints include “cannot set handler to self.” (pkqs90.github.io)
  • The standard handler on most Safes is CompatibilityFallbackHandler, which implements ERC‑1271 isValidSignature and token callbacks. From Safe v1.4.1 onward there’s also an ExtensibleFallbackHandler and the Safe4337Module which can serve as a fallback for AA. (safe-eth-py.readthedocs.io)

Why attackers care:

  • A malicious handler can make the Safe pretend to support interfaces or signatures it shouldn’t (e.g., custom ERC‑1271 behaviors), potentially tricking external apps into accepting approvals or signatures. Safe itself has guarded this area well, but the handler is still privileged enough that an unvetted one is an unacceptable risk. Safe’s own docs now label fallback handlers as potentially dangerous if not audited or allowlisted. (alchemy.com)

Bottom line: the moment your handler changes, you must know which contract it changed to—and whether that address is on an allowlist for your network and Safe version.


Legitimate reasons your fallback handler might change

  • Upgrading from CompatibilityFallbackHandler v1.3.0 to v1.4.1 on Ethereum mainnet. Canonical addresses:
    • v1.3.0: 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4
    • v1.4.1: 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99 These are published by Safe and available via safe-eth-py/safe-deployments. Your monitor should allowlist them. (safe-eth-py.readthedocs.io)
  • Enabling ERC‑4337 on a Safe ≥ v1.4.1 using Safe4337Module. In some deployments, the module acts as both Module and Fallback Handler to handle validateUserOp calls from the EntryPoint. Ensure the EntryPoint address matches your network’s supported version and that you’re on ≥ v1.4.1. (docs.safe.global)
  • Switching to ExtensibleFallbackHandler for advanced interface multiplexing. This is legitimate if you need per-function routing, but still requires strict allowlisting and audit provenance. (docs.safe.global)

If you see any other handler address, treat as unknown until proven otherwise.


What to monitor: from spammy balance diffs to Safe config events

Prioritize these Safe events by severity:

  • P0 (page the team): ChangedFallbackHandler to a non-allowlisted address; ChangedGuard to unknown; EnabledModule of unknown module; sudden ChangedThreshold decrease or RemovedOwner that drops redundancy. (docs.safe.global)
  • P1: AddedOwner with unchanged/increased threshold; DisabledModule of a known protection module; bursts of ApproveHash/SignMsg activity out-of-process with governance. (docs.safe.global)
  • P2: ExecutionSuccess of setFallbackHandler even if allowlisted (confirm intent); owner rotations aligned with calendar CABs. (docs.safe.global)
  • P3: Token balance changes not tied to confirmed governance queue items. Prefer anomaly detectors for numeric thresholds.

Also watch for L1 vs L2 differences: SafeL2 emits more events to support event-based indexing; the Safe Transaction Service may use traces on L1 and events on L2, which affects latency and data completeness. Expect 15s–120s indexing delays and no reorg waits for webhooks; always reconcile against the API. (github.com)


How to detect ChangedFallbackHandler reliably (three ways)

  1. Raw log subscription (any EVM RPC)

Use the Safe’s address and the event signature hash of ChangedFallbackHandler(address) in your filter. Example with ethers.js:

import { ethers } from 'ethers'

// 1) RPC and contract
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL)
const safe = '0xYourSafeAddress'

// 2) Event topic for ChangedFallbackHandler(address)
const topic0 = ethers.id('ChangedFallbackHandler(address)') // keccak256
// If you also want to filter to a specific handler, include it as topic[1], 32-byte left-padded.

provider.on({
  address: safe,
  topics: [topic0]
}, (log) => {
  const handler = ethers.getAddress('0x' + log.topics[1].slice(26))
  console.log(`[P0] Fallback handler changed to ${handler} in tx ${log.transactionHash}`)
})

Solidity confirms the event name and signature; topic[1] is the new handler. Add an allowlist check before routing to PagerDuty. (docs.kalychain.io)

  1. Safe Transaction Service + Events Service
  • Subscribe to the Safe Events Service SSE/webhooks; when you receive EXECUTED_MULTISIG_TRANSACTION, fetch tx details from the Transaction Service and decode if it was a setFallbackHandler call. These services are designed exactly for this and avoid heavy node polling. Note they push ASAP (no confirmation wait) and expect your system to reconcile via API. (github.com)
  1. Forta network
  • Subscribe to the Governance Threat Detection Kit and wire a custom bot or template to watch your Safe for ChangedFallbackHandler and other governance-critical events, then combine with Attack Detector 2.0 feeds to reduce false positives. This gets you ecosystem-wide threat context (sanctioned/exploiter address interactions, MEV routes) around your Safe’s changes. (docs.forta.network)

Note on OpenZeppelin Defender: it’s being sunset July 1, 2026. If you still rely on Defender Sentinels, migrate monitors to OpenZeppelin Monitor or Forta, or self-host event listeners now—don’t leave this to the last quarter. (blog.openzeppelin.com)


Allowlist known-good fallback handler addresses

Hardcoding a couple of canonical addresses saves you during incidents:

  • Ethereum mainnet:
    • CompatibilityFallbackHandler v1.3.0: 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4
    • CompatibilityFallbackHandler v1.4.1: 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99 Programmatically maintain this list via @safe-global/safe-deployments or safe-eth-py (which maps versions to networks). Your detector should compare new handler addresses against this allowlist and any internally approved custom handlers. (safe-eth-py.readthedocs.io)

For ERC‑4337 deployments, additionally allowlist:

  • The Safe4337Module address for your chain and release line.
  • The EntryPoint address family you support (e.g., 0.8/0.9 series) and verify your bundler’s eth_supportedEntryPoints output at boot. (docs.safe.global)

Production-ready monitor: 30-line rules plus paging

Policy sketch for a single Safe per chain:

  • Trigger P0 page if ChangedFallbackHandler and handler NOT IN ALLOWLIST[chainId].
  • Trigger P1 alert if ChangedFallbackHandler and handler IN ALLOWLIST but change not referenced in the current CAB/Jira ticket.
  • Always attach decoded tx, proposer, and confirmations.

Using the Safe Events Service SSE is straightforward; use the event as a cue, then call the Transaction Service to decode and verify method:

# Pseudocode flow
on EXECUTED_MULTISIG_TRANSACTION for address == mySafe:
  tx = GET /api/v1/multisig-transactions/{safeTxHash}
  if tx.data decodes to setFallbackHandler(address handler):
     if handler not in ALLOWLIST[chainId]: page P0
     else alert P1 with CAB link

Indexing expectations and reorg handling are documented—don’t rely solely on webhooks as a source of truth; treat them as a signal to fetch from the API. (github.com)


Incident playbook: What to do when ChangedFallbackHandler fires

  1. Identify the new handler contract.
  • Decode from log topic; enrich with code verification status, creator, age, and references. If unverified or recently created, raise to critical. (etherscan.io)
  1. Lock down your Safe in one or two transactions:
  • Raise threshold temporarily to N-of-N, or at least +1, to avoid a second malicious change.
  • Enable a Guard (if you have a “safe-allow” Guard that blocks setFallbackHandler except to allowlisted addresses).
  • Disable unknown modules.

Example using Safe Protocol Kit:

import Safe, { EthersAdapter } from '@safe-global/protocol-kit'
import { ethers } from 'ethers'

const provider = new ethers.JsonRpcProvider(process.env.RPC_URL)
const signer = new ethers.Wallet(process.env.KEY, provider)
const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer })
const safe = await Safe.create({ ethAdapter, safeAddress: '0xYourSafe' })

// 1) Temporarily raise threshold by +1
const current = await safe.getThreshold()
const inc = await safe.createChangeThresholdTx({ threshold: current + 1 })
await safe.executeTransaction(inc)

// 2) Optionally enable a Guard you’ve pre-deployed
// const guardTx = await safe.createEnableGuardTx({ guardAddress: '0xYourGuard' })
// await safe.executeTransaction(guardTx)

// 3) Disable suspicious modules (loop list from getModules())

Confirmations must still meet the existing threshold—plan which owners are “rapid responders.” (docs.safe.global)

  1. Revoke approvals and pause treasury programs.
  • If you run AA, confirm EntryPoint/Paymaster policies and freeze sponsorships until root cause is known. For ERC‑4337 stacks, verify your Safe4337Module is still the configured fallback and the EntryPoint matches the supported version. (docs.safe.global)
  1. Communicate externally.
  • Post a status with the handler address and whether it’s canonical. Include a list of impacted functions (e.g., ERC‑1271 signature verification paths) and any mitigations.
  1. Forensics.
  • Pull the multisig proposal metadata, proposer wallet, and signature set. If proposer is compromised, rotate owners. Keep a Forta subscription active to catch attacker funding and laundering steps in case assets move. (docs.forta.network)

Practical examples and exact signals to add this week

  • EVM log filter (low-level) for ChangedFallbackHandler(address):

    • Topic0: keccak256("ChangedFallbackHandler(address)")
    • Topic1: optional, 32‑byte handler address to scope to known-good upgrades. This is cheap, fast, and network-agnostic. (docs.kalychain.io)
  • Safe API + webhooks (operationally simple):

    • Subscribe to EXECUTED_MULTISIG_TRANSACTION; fetch and decode to detect calls to setFallbackHandler(handler). Expect sub‑minute to 1–2 min latency under load. Your endpoint must respond in <2s. (github.com)
  • Forta policy layer (threat intelligence):

    • Subscribe to Governance Threat Detection Kit and Attack Detector 2.0. Use these to correlate your Safe’s change with known exploiter interactions or Tornado funding patterns. Route P0s straight to the incident channel. (docs.forta.network)
  • Event noise suppression:

    • Keep “Token Balance Changed” as P3 informational. Zero‑value transfer spam is hidden by default on major explorers and is not a compromise signal. Run an anomaly detector for large ERC‑20/721/1155 outflows instead of a raw “balance changed” trigger. (coindesk.com)

ERC‑4337 interplay: extra checks when your Safe is AA‑enabled

If you use Safe as an ERC‑4337 account:

  • Safe must be v1.4.1+.
  • The Safe4337Module typically serves as both Module and Fallback Handler. Verifying the fallback handler’s address AND the EntryPoint version (0.8/0.9) is essential; misconfigurations can brick validation or open unexpected code paths. Your monitor should assert:
    • handler == Safe4337Module address for your chain release
    • entryPoint in eth_supportedEntryPoints
    • module enabled status remains true. (docs.safe.global)

Reference addresses and how to keep them up-to-date

  • Use @safe-global/safe-deployments to fetch CompatibilityFallbackHandler/ExtensibleFallbackHandler addresses per chain and version in CI, then publish an internal allowlist artifact your bots read at runtime. For Ethereum mainnet:
    • v1.3.0 CFH: 0xf48f…fe5e4
    • v1.4.1 CFH: 0xfd07…9c99 Cross‑check periodically with safe‑eth‑py docs and the deployments repo. (safe-eth-py.readthedocs.io)

A minimal risk matrix you can copy

  • P0: ChangedFallbackHandler not in allowlist; ChangedGuard unknown; ChangedThreshold decreased; EnabledModule unknown.
  • P1: ChangedFallbackHandler allowlisted but not in CAB; AddedOwner without threshold increase; bursts of ApproveHash/SignMsg off cadence.
  • P2: DisabledModule of a known module; benign owner rotations within CAB window.
  • P3: Token balance change without structural config change.

Event names and docs: ApproveHash, SignMsg, Added/RemovedOwner, ChangedThreshold, Enabled/DisabledModule, ChangedFallbackHandler. (docs.safe.global)


Implementation notes and emerging best practices

  • Don’t await confirmations before alerting. Safe’s own event/webhook infra emits ASAP; your job is to page fast and then reconcile. (github.com)
  • Chain differences matter. On L2s, event-based indexing is the default; on L1, trace-based indexing increases infra complexity and can affect latency—budget for this in SLAs. (github.com)
  • Defender migration. If you’re on OZ Defender Sentinels today, plan your shift to OpenZeppelin Monitor or Forta before July 1, 2026. Prioritize monitors for Safe config events and governance timelocks. (blog.openzeppelin.com)
  • Document handler provenance. For any non-canonical handler, archive audit links, code hashes, deployment tx, and owner keys. Require CAB approval to rotate handlers, even for upgrades. (docs.safe.global)

What 7Block Labs ships for clients

  • A pre-built “Safe Config Guardrail” that:
    • Subscribes to ChangedFallbackHandler/Guard/Module/Owner/Threshold events across your chains.
    • Maintains a CI‑updated allowlist from safe‑deployments and your CAB.
    • Pages P0 within seconds, posts decoded tx detail, and can optionally auto‑submit a “raise threshold” transaction from a reserved emergency owner.
  • Forta subscriptions and custom bots that correlate treasury config changes with attacker funding/exfil patterns, reducing false positives. (docs.forta.network)
  • AA-aware monitors validating Safe4337Module and EntryPoint versions per chain. (docs.safe.global)

The takeaway

  • Stop chasing “Token Balance Changed.” It’s background radiation. (coindesk.com)
  • Start paging on ChangedFallbackHandler (and peers like ChangedGuard/EnabledModule/ChangedThreshold). This is where treasury takeovers begin or are prevented. Safe’s event surface is stable; your monitors can be precise. (docs.safe.global)
  • Ship the allowlist, the log filter, and the incident playbook this week. Your future self—and your token holders—will thank you.

Sources and further reading

If you want us to wire this for your treasury, we’ll deliver the monitors, allowlists, and runbooks in under two weeks—including AA/4337 checks and chain‑specific nuances.

Like what you're reading? Let's build together.

Get a free 30‑minute consultation with our engineering team.

Related Posts

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2025 7BlockLabs. All rights reserved.