Before Mainnet, Remove the Privileges an Auditor Cannot Make Safe
Establish the problem with technical depth
The worst pre-deployment mistake in Web3 is not missing one more bug. It is shipping authority that turns dangerous the moment one signer, one initializer, or one upgrade path lies. That is the uncomfortable divide between code that is merely reviewed and systems that are launchable. Founders usually feel this as schedule pressure. CTOs and Solidity engineers feel it as architecture debt. Investors feel it later as an avoidable loss that looked impossible right up until it happened.
The Ronin breach disclosed on March 29, 2022 is one of the clearest examples. Sky Mavis said attackers drained 173,600 ETH and 25.5 million USDC after compromising five of nine validator signatures. Ronin's own write-up says Sky Mavis controlled four validators, and a temporary Axie DAO allowlist path that had been created in November 2021 was never revoked after it was no longer needed. That was not a last-minute coding slip. It was authority that stayed broader than the system could safely tolerate.
Nomad exposed the same category from the upgrade path instead of the validator path. In Nomad's August 5, 2022 root cause analysis, the team explained that an implementation bug let the Replica contract accept messages that had never actually been proven. In its August 17 recovery update, Nomad said the August 1 exploit cost more than $186 million. The important lesson is architectural. A bridge verification rule existed in the code, but initialization made that rule weaker than the team believed. A control that looks present and behaves absent is one of the most dangerous states a protocol can ship.
The Bybit incident timeline published on March 3, 2025 drives the point into the current cycle. Bybit says one Ethereum cold wallet lost $1.46 billion on February 21, 2025 after attackers exploited the Safe multisig user interface and changed the wallet logic through a malicious transaction. A multisig means multiple signatures are required before an action executes. That only helps if those signers are independently verifying the same reality. If every signer sees the same compromised rendering, the threshold is theater.
This is why pre-deployment security is badly named. The problem is not only what happens before deployment. The problem is what gets decided before deployment about who can change the system later, how those changes are approved, and how much correlated failure the protocol can survive. Once you see that, "did we get an audit?" stops being the main question. The harder question is whether the protocol's authority model still makes sense after the first laptop compromise, the first rushed upgrade, or the first operator who clicks through a misleading transaction.
The mechanism, the mistake, the misunderstanding
Most teams still model privileged power too loosely. They feel safer when they can point to onlyOwner, a proxy admin, or a 5/9 signer threshold. Those are not controls by themselves. They are labels on top of a deeper trust design. The real question is how many independent failures must happen before dangerous authority becomes reachable.
Three pre-deployment mistakes keep recurring.
The first is concentrated authority. Ronin looked distributed on paper, but its own disclosure showed that operational reality was far more centralized than the headline threshold suggested. If one company controls most validators, or one engineering group controls proposal creation, signer devices, and transaction interpretation, the system is not as decentralized as the dashboard implies.
The second is unsafe initialization and upgrade assumptions. Nomad's root cause is a brutal example because the code path was not visually exotic:
function initialize(bytes32 _committedRoot) public initializer {
committedRoot = _committedRoot;
confirmAt[_committedRoot] = 1;
}
That pattern is only safe if the initial root has the semantics you think it has. In Nomad's case, an empty Merkle tree produced bytes32(0), and the initialization flow ended up pre-approving the zero root. After that, verification still existed syntactically, but not meaningfully. This is where too many teams get hurt. They review the function body and forget to review the assumptions that make the function body safe.
The third mistake is unbounded emergency power. Teams add pause rights, rescue functions, hotfix upgrades, whitelists, special routers, or launch-only exemptions because they want flexibility. It is also where protocols quietly smuggle centralized failure back into a system that markets itself as trust-minimized. If emergency powers are broad, permanent, and weakly monitored, the protocol is carrying a hidden short position on operator judgment.
The deeper misunderstanding is thinking an audit can compensate for those choices. It cannot. An auditor can tell you whether a given implementation is internally coherent. An auditor cannot make an overpowered admin role safe by sheer force of prose. An auditor cannot create independence between signers who all trust the same interface. An auditor cannot revoke stale privileges after the team forgets they exist. Those are design and operations problems. If they are wrong before mainnet, the protocol is already late.
What good looks like
Good pre-deployment security starts with a privilege map, not a scan result. Before launch, teams should be able to enumerate every role that can mint, upgrade, pause, whitelist, change fees, change oracle inputs, move treasury assets, or bypass normal user flows. If that list cannot fit cleanly into an approval document, the system is already too hard to reason about. A privilege map is exactly what it sounds like: a plain-language inventory of who can do what, under which conditions, with which blast radius.
Good also means shrinking authority until it matches intent. OpenZeppelin's access control guidance explicitly calls out the principle of least privilege: each component should have only the permissions it actually needs. In practice, that means separating proposal, approval, and execution roles instead of collapsing them into one ops group. It means revoking temporary launch helpers on a schedule, not "later." It means using signer diversity that is real, not decorative. If one compromise can poison every human checkpoint in the flow, you do not have layered defense.
Deployment and upgrade paths need the same suspicion as the core contracts. OpenZeppelin's upgradeability documentation warns that violating storage-layout rules can mix up stored values and lead to critical errors. That should end the habit of treating upgrade diffs as lighter review work. Teams should rehearse the exact deployment and upgrade transactions on a fork, decode the calldata independently, validate initializer arguments, and review storage changes as if they were treasury code, because they are. A proxy is just a long-lived authorization surface wrapped around business logic.
Testing also has to move up a layer. Foundry's invariant testing docs describe randomized sequences of calls that assert invariants after each function call. An invariant is a condition that must remain true across every valid state transition, not just the happy path in a unit test. Solvency is an invariant. Supply conservation is an invariant. "Only the timelock can upgrade" is an invariant. If your test suite proves deposits work but never stresses the upgrade path, signer path, or pause path, it is proving the demo, not the protocol.
Finally, good looks like designing emergency controls that are bounded and observable. A pause function should have a defined scope. A rate limit should have explicit thresholds. A hotfix signer should page humans the moment it acts. The Safe Ecosystem Foundation's statement after the Bybit attack said the attack reached Bybit through a compromised Safe developer machine that proposed a disguised malicious transaction, while external researchers did not find a vulnerability in the Safe smart contracts themselves. That is exactly why transaction rendering, signer independence, and out-of-band verification belong in the launch checklist. The signing path is part of the product.
ChainShield's angle
ChainShield's view is blunt: launch readiness is mostly an authority question that gets disguised as a code question.
That is why we care so much about diff-aware review, privilege mapping, signer-flow verification, and upgrade simulation. The protocol you deploy is not just the Solidity that passed review on a static day. It is that Solidity plus every role that can mutate it, every interface that can misrepresent it, and every operational shortcut that can silently widen authority after the audit is over.
The teams that survive the next cycle will not be the ones with the prettiest security PDF. They will be the ones that can answer a harder question before mainnet: which privileges are truly necessary, which ones expire, which ones are independently verifiable, and which ones would still look safe if an attacker controlled one machine in the chain? If a protocol cannot answer that clearly, the problem is not that it needs one more audit. The problem is that it is about to deploy authority it does not understand.
ChainShield Discovery Runs are designed to identify high-risk issues quickly, validate what matters, and give engineering teams a faster path to remediation.
Request Security Quote