new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Smart Contract Auditor · all subjects

Access control

4 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Why is tx.origin authentication always a finding?

`require(tx.origin == owner)` authenticates the EOA that STARTED the transaction chain, not the immediate caller. Attack: trick the owner into calling a malicious contract (phishing site, airdrop bait); that contract calls the victim contract, `tx.origin` is still the owner, checks pass, funds move to the attacker. This is a standard medium/high finding with no legitimate use in modern Solidity. Fix: always use `msg.sender`; if you need meta-transaction sender identity, use ERC-2771 (`_msgSender()`) with a trusted forwarder, never `tx.origin`. Detection signal: grep `tx.origin` — any hit is reportable. Related pitfall: `tx.origin` used for "contract vs EOA" detection (`tx.origin == msg.sender`) breaks with smart wallets and account abstraction (ERC-4337 / EIP-7702 delegations), so don't rely on it for that either — as of early 2026, 7702 adoption makes this check actively wrong.

How was the Ronin bridge actually compromised?

Ronin (March 2022, ~$625M) was NOT a smart-contract bug — it was key management. The bridge validator set required 5 of 9 signatures; the attacker (Lazarus Group, per US Treasury) compromised 4 of Sky Mavis's validator keys via social engineering, plus 1 more through a stale allowlist: the Axie DAO validator had been given RPC signing access months earlier and the permission was never revoked. Lesson for auditors: multisig security is (threshold, key independence, operational hygiene), not just the contract. When auditing a bridge or upgradeable protocol, ask: who holds the admin keys, are they on one machine/org, is there a timelock, can a single compromised deployer rug everything. A 5-of-9 where one entity controls 5 keys is a 1-of-1. Detection signal: `onlyOwner` mint/upgrade/pause powers with an EOA or low-threshold multisig and no timelock — flag it as centralization risk even if the code is "correct".

What is an unprotected initializer in an upgradeable contract?

Implementation contracts for proxies are deployed with EMPTY state; their logic contract's `initialize()` (replacing the constructor) is callable by anyone if left unprotected on the implementation itself. An attacker calls `initialize()` on the implementation, becomes its owner/admin, then `selfdestruct`s it or — worse — uses a `delegatecall` in an admin function to hijack every proxy pointing at it. This is how several real incidents unfolded and why OpenZeppelin's docs tell you to lock the implementation: call `_disableInitializers()` in the implementation's constructor. Detection signals: `initialize` functions without `initializer` modifier; missing `constructor() { _disableInitializers(); }` in upgradeable contracts as of OZ 4.x+; any `delegatecall` reachable from an admin function (parity-wallet-style). Severity: critical when the implementation has any privileged surface; informational if the implementation has no state-mutating admin functions at all.

Is missing whenNotPaused on a state function a real bug?

Only sometimes — report it as medium at most unless funds can actually move during the pause. The point of `Pausable` is incident response: when an exploit is detected, admin pauses and the attack surface freezes. If `deposit()`/`withdraw()` lack `whenNotPaused` while `emergencyPause()` exists, the pause is theater — the attacker keeps draining. That's a real finding. But flagging a view function or a cosmetic setter for missing `whenNotPaused` is noise. Detection signal: contract inherits `Pausable` and defines `pause()`/`unpause()` entry points; grep which external functions lack the modifier and check whether each moves value or state that an ongoing exploit would touch. Also check the inverse: functions that should work WHEN paused (e.g., emergency exit for users) but have `whenNotPaused` — some protocols deliberately allow `withdraw` during pause; decide from the spec, not from a linter.

Give your agent this brain