Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how
Zealynx NewsletterWeek 03 · June 30, 2026

The bytecode you audited is not the bytecode that runs.

Weeks 01 and 02 were about a key that could spend and a key that could sign. This week the key does something worse: it replaces the contract. Humanity Protocol lost roughly $36M when three valid signatures upgraded a token proxy and minted 300M new tokens, and the audited logic never had a say.

01Finding of the week

Whoever holds the upgrade key owns every line we just signed off.

An audit certifies bytecode at a commit hash. On an upgradeable proxy, that bytecode is a pointer, not a promise. The proxy's storage holds an implementation address, and whoever can change that address can swap the reviewed logic for anything, including a mint function that did not exist at review time. This is the finding we file on almost every upgradeable engagement, and it is the one clients most often want to push down to "Low, it's behind the multisig." Humanity Protocol just paid roughly $36M to show why it is not Low.

The finding class is under-guarded upgrade authority. A _authorizeUpgrade gated only by onlyOwner, where the owner is a 2-of-3 or 3-of-5 multisig with no timelock, is not a control. It is a single-transaction path from key custody to total protocol takeover, and it sits outside the code you were paid to review. We score it High when the upgrade executes in one call, Medium when a timelock exists but is short or admin-bypassable, and in every case we check whether the "admin" is a genuine on-chain threshold or an EOA standing behind a multisig UI that a single machine compromise unwinds.

Remediation, in one place: put upgrades behind an on-chain timelock long enough for a human to cancel, require the new implementation to be proposed and queued rather than executed in the same transaction, separate the propose key from the execute key so one stolen signer cannot do both, and write an invariant that asserts no implementation swap can land before the delay has elapsed. The EIP-1967 implementation slot is public. Monitor it and alert on any write, because the first person to notice the swap should be you, not the person who did it.

You do not audit a contract. You audit the contract plus whoever is allowed to replace it.

02Exploit of the week, autopsied

Humanity Protocol: three valid signatures upgraded the token and minted 300M.

Date
June 2026
Loss
~$36M, from roughly 300M H minted out of thin air and sold into the market
Chain
Ethereum mainnet, H Token ERC-20 upgradeable proxy
Vector
Upgrade-authority compromise, malicious implementation swapped in, unauthorized mint
On-chain gate
Multisig upgrade authority, no timelock between proposal and execution
Entry point
Disputed. Insider theory vs signer-malware account (see note below); the on-chain mechanism is agreed
Attacker EOA
Unconfirmed, not stated here

A note on what is and isn't confirmed

The entry point is disputed. ZachXBT read the timing and fund flow as consistent with an insider; the team's account attributes it to malware that compromised a signing machine. Those two stories disagree on how the keys were reached, and we are not resolving that here. What both accounts agree on, and what the chain shows, is the mechanism: valid upgrade authority replaced the H Token implementation and the replacement minted. We autopsy the mechanism, not the attribution, and the engineering lesson holds whichever way the entry point lands.

What the contracts verified correctly

Nothing in the token logic was bypassed. The proxy did exactly what an upgradeable proxy is written to do: it checked that the caller held upgrade authority, it checked that the signatures met the multisig threshold, and it pointed at the new implementation. There was no reentrancy, no signature forgery, no broken access-control modifier on the mint path. From a contract-only review, the upgrade succeeded because it was authorized. This is the trap. The attacker did not defeat the audited code. They swapped it for code no auditor ever saw.

What the upgrade path did not have

There was no delay between "signatures verify" and "new logic is live." A valid multisig approval was the only gate, and once it cleared, the implementation changed in one transaction. No timelock to give a human the minutes needed to cancel. No separation between the key that proposes an upgrade and the key that executes it. No supply invariant that would have made a 300M mint impossible regardless of which implementation was active. The illustrative shape of the gap, written as a generic UUPS token rather than Humanity's actual source:

// the only gate on replacing every audited line
function _authorizeUpgrade(address newImpl) internal override onlyOwner {}

    // missing: a timelock between proposal and execution
    // missing: require(newImpl == queued && timelockElapsed(newImpl));
    // once the owner keys sign, the swap is live in one tx

// the swapped-in implementation then shipped what review never saw:
function mint(address to, uint256 amount) external {
    _mint(to, amount); // no cap, no role, no audited path
}

The test we would have written

For a closed contract you fuzz the logic. For an upgradeable one you also fuzz the upgrade path, because that path is how the logic changes. The property is not "does the upgrade authorize correctly." It is "no implementation swap lands without the delay elapsing, and total supply only grows through the path we reviewed."

function invariant_upgradeRequiresTimelock() public {
    // handler grants the fuzzer the upgrade role
    // and lets it attempt setImplementation()
    assertTrue(pendingImpl == address(0) || timelockElapsed(pendingImpl));
}

function invariant_supplyOnlyViaAuditedPath() public {
    // totalSupply can only grow through the mint we reviewed
    assertEq(token.totalSupply(), mintedThroughAuthorizedPath);
}

An invariant harness that hands the fuzzer the upgrade role and still cannot swap the implementation without the timelock, or grow supply outside the reviewed path, is the operational half of an upgradeable-token engagement. If it swaps on the first round, the timelock and the propose/execute split are the fix, and they cost less than 300M tokens.

Why it's the same class as this week's finding

Week 02's Gravity drain was a valid signature over a transfer. This is a valid signature over an upgrade, which is the more dangerous of the two, because it does not move funds within the rules, it rewrites the rules. Mint, not drain, is the tell: the attacker created supply the audited contract had no function to create, because by the time the mint ran, the audited contract was gone. Upgrade authority is access control that outlives the audit, and it is only as strong as the delay and the key separation standing behind it. Humanity had neither.

03Tooling note

Read a proxy upgrade with cast before the post-mortem does it for you.

Foundry's cast is the fastest way to see what actually runs behind a proxy, and it needs nothing but an RPC URL. The implementation address lives at a fixed EIP-1967 storage slot, so you can read it straight off-chain, diff the old and new implementation bytecode, and replay the upgrade transaction to watch the swap land. For Humanity, the same three commands turn "300M appeared" into "the implementation at this slot changed in this tx, and here is the code it changed to."

# EIP-1967 implementation slot: what the proxy points at right now
$ cast storage <proxy> 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc

# fingerprint the old vs new implementation bytecode
$ cast code <oldImpl> | sha256sum
$ cast code <newImpl> | sha256sum

# replay the upgrade tx and watch the implementation change
$ cast run <upgradeTxHash> --trace

Reading the slot is the forensic version. The preventive version is asserting it in CI: Krait's upgrade-authority boundary generates the timelock and supply invariants above as a standing Foundry harness, so "can a signer swap the implementation without the delay" runs on every commit instead of after the incident.

04From the Zealynx blog

Proxy-relevant reading from the Zealynx archive.

  • Article
    Proxy upgradeability security checklist. The upgrade-authority and timelock section is the part that maps directly to this week's finding: who can call the upgrade, and what stands between the call and the swap.
  • Glossary
    Proxy pattern. The one-page mechanics of why the implementation address is the whole attack surface: storage lives in the proxy, logic lives in a swappable target, and the slot is the seam.
  • Methodology
    How fuzz testing strengthens smart contract security. The invariant-harness section is the how-to behind the two properties in Block 02: bookkeeping ghost variables and asserting supply and upgrade constraints under a fuzzer.
05Academy update

New in the Academy: Shadow Arena, timed audits on real forks.

Zealynx Academy shipped Shadow Arena: it drops you into forks of past public contests on 2, 4, and 7-day windows and scores your findings against the real results. A confirmed High is worth 75 Lynx, a Critical 100, and duplicates of the same root cause count once, so it rewards depth over volume. It is the closest thing to auditing an upgrade-authority bug like this week's under a clock, before it is your protocol on the line. Still free, no paywall. New drops land first on the updates page.

06Housekeeping

The Ethereum Foundation's $1M audit subsidy (via Areta, up to 30% of cost) is still open, and Zealynx Round 02 grants are open below if you would rather we cover the audit directly.