Notes / Audits and snapshots
Source: data/audit_2026-10-11-second-review.md in the repository, rendered here without changes. Source file
Six independent reviewers read the shared scoring library, the per-chain scorers, the dashboard script, the Solana and Zcash code and the push scripts, each told to refute the code rather than confirm it. Every finding below was re-checked against the code before being acted on. The score fixes touch failure and rotation branches, which a healthy read does not take; they have not been replayed against live Robinhood state, so the next Robinhood re-push starts with a dry-run preview that shows whether any published value moves.
| Where | Defect | Fix |
|---|---|---|
scripts/lib/safe_modules.py | Module pagination restarted from the wrong cursor on Safe v1.3.0, so a Safe v1.3.0 with more than one page of modules lost one module at each page boundary | Resume from the last returned module; test added that fails on the old code |
scripts/lib/push_guard.py | A crossExposureScore defaulted to 100 after a failed computation was held only if the composite moved, and it never does; a rotated stock-token admin and an unreadable Spark delay() published without a hold | A default note holds the entry whenever the published value differs; both unresolved branches now carry an "unresolved authority" note |
scripts/lib/scorers.py, stock tokens | A role check that came back False or unread scored the admin key 40, above the 20 floor for an unresolved read | 20, with a note |
scripts/lib/scorers.py, two Spark scorers | An unreadable delay() scored the timelock 60, the best case | 0: unknown is never better than a confirmed zero delay (10) |
dashboard/index.html | A slow read could paint its scores under the tab the visitor had moved to; an empty eth_call showed as a green "0 targets" or a STRONG row; a malformed #chain= stopped the page; RPC-supplied strings reached the HTML unchecked | Load-sequence guard, address and length checks, ignored malformed hash, numeric coercion |
scripts/update_scores.py | A snapshot file that existed but could not be parsed was treated as a first publish, which switched the completeness and alert guards off; it was also written non-atomically | The run refuses; the file is written then renamed |
push_guard; --accept-held, --accept-missing and --skip-published-check are the explicit overrides.)Ratio threshold is rounded with ceil where the DAO contract uses floor plus one (no tracked DAO is affected).zec.omft.near is scored from its Requestor role, which in the live policy cannot approve a call; the approving path is Approver, which scores slightly better (10/20, composite 10, instead of 5/18, composite 7). The node-lineage count counts two versions of the same client as two lineages.scripts/lib/scorers.py for several chains, so a comment change in the Robinhood scorer changes every oracle's hash. It never hides a change; it can report one that did not happen.Reproduced with fake reads (no network, no writes). Fixing them changes numbers on a live oracle, so each waits for a dry-run preview and a re-push.
setPeer bypass lowers the score) and getMinDelay and the Safe threshold are literals, not reads.pauseGuardian scores 65 against 60 for a Safe.dao_is_proposer is read but does not enter the score, and a 7-day delay scores 40 against 60 for 4 days.timelock() is not read.voting_can_execute, emergency mode), Arbitrum Security Council (has_exec_role), Echo eBTC (is the compromised EOA still admin), Kuru (MarginAccount.owner())._crossEcosystem is hard-coded for two vaults.ACCEPT_* guard in repush_all_oracles.sh skipped step names with a digit (l1).update_scores.yml runs weekly (Sunday 06:00 UTC) and pushes Robinhood Chain testnet, behind the same push guard and completeness check. It has failed at that check on 2026-10-04 and 2026-10-11 because one Robinhood pool-registry read is refused by the RPC.score_all skips the scorer), and one value moved with the read (io, oracleAuthorityScore 58 then 15). Three dex keys (io, para, mkts) had not been refreshed since 2026-09-24 and were stale on chain. push_guard.enforce now takes require_all_tracked, used by the Hyperliquid push: it refuses a real send while the oracle tracks a key the scorer did not return (--accept-missing for a retired one; two retired keys are listed in push_guard.RETIRED_ON_PURPOSE).Open, not changed before the submission (each one moves a published score or needs a re-push):
0x5a4e…d4dd, 4-of-7 on Tempo and 4-of-6 on Base, six signers in common), yet the Tempo target reports crossExposureScore 100. Morpho Blue on Base reads 80 (shared) while Morpho Blue on Ethereum, with the same owner, reads 100. Same for Grove on Base and Monad. The Safe 0x5a4e…d4dd appears in none of this project's signer registries or dated snapshots (searched the whole repository), so nothing compares it with the other chain's committee and the target reports 100, which means "not checked", not "not shared". crossExposureScore is not part of the composite, so no composite moves; a fix needs the Safe added to the registries in both directions and a re-push of the affected oracles. Traced on 2026-10-12: cross-ecosystem overlap is not computed from the chains. Each ecosystem scorer carries hand-added dated snapshots of committees found by hand (_KNOWN_* constants: Morpho Blue owners, the Compound pause-guardian Safe, the Steakhouse Safes) and caps a target at 80 when the owners read this run equal one of them; Tempo's cross_exposure knows only the overlaps inside Tempo plus the Morpho Blue snapshot, and Base has no snapshot for the 0x5a4e…d4dd committee. Both Tempo Earn and gtUSDCp therefore read 100 (live values: Tempo 0xC609… 100, Base 0xee8f… 100), although the Safe is the same one on both chains. crossExposureScore is outside the composite, so no composite moves. Cheapest fix: add a snapshot of that committee (4-of-7 on Tempo, 4-of-6 on Base, six signers in common) to both scorers and cap both at 80; the lasting fix is one shared registry of committees built from the Safe of every tracked target, instead of per-ecosystem constants. Either needs a re-push of the Tempo and Base oracles. The Morpho Blue (80 on Base, 100 on Ethereum L1) and Grove (Base against Monad) differences look like the same cause, a snapshot on one side only, but were not traced.second_opinion.py): Flock Credit Vault governance(), GMX V1 gov(), Uniswap V2 and PancakeSwap V2 feeToSetter(), Euler upgradeAdmin(). Convex: the reader stops at the second hop where the chain has a third, ending at a 3-of-5 Safe.Not an audit, not investment advice. All notes · Back to the dashboard.