# Audit brief - disorderly Prepared 2026-09-10 for an independent security review before the mainnet deployment. It says what the system is, what we ask you to review, what has been reviewed already, what we know is open, and how to build and run it. The repository at the tag named in section 9 is the package; this file lives in it as `docs/AUDIT-BRIEF.md`, the public evidence index as `docs/EVIDENCE.md`. A readiness review of an earlier draft of this brief (`docs/AUDIT-BRIEF-READINESS-2026-09-09.md`) found three implementation gaps and several inaccurate statements. The gaps are fixed in the tagged snapshot (section 4) and the statements corrected here; the review stays in the package as part of the record. ## 1. What the system is disorderly is 1,111 ERC-721 tokens on Ethereum. Each token is an autonomous AI agent with its own memory and a temperament derived from its artwork. 100 tokens are council seats that vote on proposals and decide awards, gates and disputes; 1,011 are operators that bid for and perform mandate work. Proceeds of the mint become a treasury held in a Gnosis Safe. Trust model, stated plainly: the application computes what the record says should happen; the Safe's signers execute it. Nothing on chain constrains the Safe to the record. Its threshold signers are trusted to sign only what the published record instructs and to refuse anything else; that is an operating rule of DISORDERLY LLC (Florida), the entity that holds the signing keys, not a property the contracts enforce. A compromised application can prepare wrong data, and the signers are the check on it. The record is tamper-evident and publicly verifiable; it is not trustless. The money flow: a passed proposal posts a mandate with the terms the passed document states (budget, fee per accepted unit, gate criteria); operators perform and peer-review work; fees and a commission on net profit are computed into a Merkle payout table; the Safe funds a cycle on the PayoutDistributor with that root; holders claim their own lines. Votes, decisions, deliberations and payout tables are published and hashed. What is anchored where: | artifact | anchor | |---|---| | deliberation commitment (document, dissents, window, quorum, prompt) | `ProposalRegistry.commitDeliberation`, before the vote closes | | closed vote record with tally and every ballot and signature | `ProposalRegistry.publish` | | payout table for a cycle or settlement | `ProposalRegistry.publishCycle` and the funded `PayoutDistributor` root | | operational decisions (awards, gates, disputes, continuation), work records, calendars | files in the cycle bundle, archived on Arweave; hashed into the bundle, not anchored individually | Site: https://disorderly.ai. Deployments are in `ADDRESSES.md` section 4; there are two on Sepolia (a legacy collection that holds the rehearsal tokens, and the current hardened contracts) and none on mainnet yet. ## 2. Scope ### 2.1 Solidity (primary) | contract | lines / nSLOC | role | |---|---|---| | `contracts/contracts/Disorderly721.sol` | 511 / 272 (was 488 / 264 at the 2026-09-12 tag) | the collection: two allowlisted tiers plus public mint, exact payments, provenance, commit-reveal with a 100-block delay and a per-tier draw, metadata freeze, two-step ownership, immutable proceeds destination | | `contracts/contracts/PayoutDistributor.sol` | 232 / 117 | Merkle payout cycles funded by the owner (the Safe): open, claim, deadline, sweep; liabilities never exceed funding; no dependency on NFT ownership by design | | `contracts/contracts/ProposalRegistry.sol` | 365 / 204 | write-once anchors: deliberation commitments, closed vote records with tallies, cycle records with payout roots, append-only amendments; it requires a commitment before a publication but does not itself enforce the vote window, quorum, ballot signatures or root equality with the distributor | | `contracts/contracts/RoyaltyRouter.sol` | 408 / 200 (was 394 / 199) | secondary royalties: a conversion reserve up to a USD threshold read from a price feed, then a fixed split; WETH unwrap; failed-recipient handling with a gas-bounded release; ownerless, three immutable destinations | | `contracts/contracts/DisorderlyAgentCollection.sol` | 202 / 97 | optional ERC-8041 wrapper for agent registry membership; not required for launch; pin the draft revision if reviewed | Tests: `contracts/test/` (232 passing in the tagged baseline; 259 with the supplementary Pashov-workflow tests described in section 4). Four integration tests drive the Node server against a local chain: Allowlist, CyclePipeline, ManualBallot and LaunchFixes. ManualBallot casts a signed manual ballot over HTTP, closes, publishes to the local registry and claims from the local distributor with an injected ownership client and a legacy-format deliberation; LaunchFixes covers commitment timing, reopened windows, transferred-seat payouts and claims. No single test mints a real NFT and runs the full version 3 flow end to end; the Sepolia rehearsals in `docs/EVIDENCE.md` are that evidence; note that no rehearsal has yet run a version 3 record from a manual vote through a funded payout and a claim in one cycle (cycle 7's mandate was killed before payout, cycle 8's vote failed); the funded payout and claim paths were rehearsed in cycle 4 and the mandate 3 settlement under the legacy record format, listed in the manifest. In scope from `contracts/scripts/`: `deploy*.js`, `verify.js`, `allowlist.js` and `build-allowlist.js` (they construct the mint's authorization inputs); the remaining files there are art tooling, out of scope. Config: `contracts/hardhat.config.js`. ### 2.2 Application boundary (secondary, requested) The contracts trust inputs the application computes. Review the code that produces them and enforces the off-chain rules the contracts cannot. Explicit include list: | area | files | |---|---| | ownership and ballot execution | `server/api.js` (holder mutations), `server/auth.js`, `server/chain.js`, `server/ballots.js`, `server/quorum.js`, `server/proposal.js`, `server/mandate-terms.js` (deployment calendar lookup and binding execution terms), `server/agents/modes.js`, `scripts/deliberate.js` | | commitment and closed record | `server/commitment.js`, `server/agents/deliberate.js`, `server/proposal-record.js`, `server/cycle-publish.js` | | store rules, schema, migration, deployment binding | `server/store.js`, `server/store-pg.js`, `server/schema.sql`, `server/migrate.js`, `server/runtime.js`, `scripts/runtime.js` | | operational decisions and work authorization | `server/decisions.js`, `server/agents/decide.js`, `server/agents/bid.js`, `server/agents/workflow.js`, `server/agents/work.js`, `server/agents/ideate.js` (output validation and the terms it produces) | | payouts, settlement, recording, recovery | `server/ledger.js`, `server/cycle.js`, `server/participation.js`, `server/settle.js`, `server/cycle-safe.js`, `server/chain-verify.js`, `scripts/cycle.js`, both supervisor modes in `scripts/cycle-run.js` | | independent acceptance and evidence | `scripts/verify-proposal.js`, `scripts/verify-cycle.js`, `scripts/archive.js`, `server/arweave.js` | | wallet-facing pages | signing, account and network handling, and rendering of untrusted text in `dashboard.html`, `app/index.html`, `mint.html`, `claim.html`, `wallet-safety.js` | | host boundary | `deploy/nginx.conf`, `deploy/*.service`, `deploy/sudoers.disorderly`, `deploy.sh` | Model prompts are not in scope for quality. Validation of model output at the security boundary is: what an agent's structured output can change about money or authorization, and prompt or tool injection through proposal text, dissents, deliverables and tool results (`server/agents/prompt.js` states the boundary; the parsing and persistence around it is in the include list). Out of scope: the browser game, the waitlist and mail, the art pipeline. Agree the application scope in writing and quote it separately if needed; it should not become a Solidity-only review with a glance at the callers. ## 3. What we most want checked 1. Nothing can move treasury funds except a Safe transaction the published record instructs: no path from server, holder or RPC to a payout the record did not compute. The signers are the enforcement; see section 1. 2. A holder can act only for a token they currently own, at the moment of the action: cast, approve, delegate, bid, join, submit. Earned payouts belong to the address in the published leaf and are claimable by anyone for that address; a later transfer of the token does not revoke them, by design. 3. A proposal cannot pass without the required number of cast council ballots under the quorum frozen into the document hash; zero ballots never pass; the deliberation record alone never decides. 4. The commitment anchored before the vote closes binds the exact document, dissents, window and quorum the vote ran under, and the application refuses to apply agent ballots unless the on-chain commitment sits inside that window; a late or absent commitment cannot be repaired; records are append-only with the original preserved. The registry enforces existence and write-once; the application enforces timing and terms. 5. The payout table reconciles to the wei, pays only for recorded work or a recorded ballot, cannot double-pay a transferred seat, and matches what the distributor funds. The distributor cannot read NFT holdings, which prevents nothing by itself; the application's table construction is what prevents holding-based payment, and that is what to review. 6. A payout is recorded only when the chain shows it exactly as prepared: funded root and total, published record hash, both transactions mined to the right contracts (`server/chain-verify.js`, one rule for every path). 7. Authorizations bind their deployment: version 2 ballots, delegations and action approvals name domain, chain and collection; a signature for one deployment does not verify on another. 8. The reveal cannot be steered except through the documented block-proposer influence, and expires and recommits as documented. 9. Deployment isolation: a mainnet runtime cannot resume or reuse rehearsal files, batches or database rows. ## 4. Prior review, remediation and evidence - Supplementary AI review of all five tagged contracts using the pinned Pashov Solidity Auditor v3 workflow: `docs/audits/pashov-2026-09-10/REPORT.md` and `docs/audits/pashov-2026-09-10/README.md`. Nine specialist agents and three coordinator passes covered twelve specialties. No new exploitable issue was confirmed under this brief's trust model; no production contract was changed. The combined suite passed 259 tests, including 27 new checks. Reproduced conditional recipient-gas failure and nonuniform reveal probabilities are retained for your assessment, with explicit dispositions and source hashes. This is not an audit by Pashov's human team and does not replace this engagement or its application scope. - Static analysis passes on the same contract bytes, 2026-09-11: `docs/audits/tools-2026-09-11/README.md` triages Slither 0.11.6, Semgrep with the Decurity rules, Aderyn 0.6.8 and 4naly3er against this brief and the earlier reviews, with raw output beside it. No new finding requiring a change; the accepted Slither results and the tools' false positives are listed so they are not reported as discoveries. - Property fuzzing and symbolic checks on the same bytes, 2026-09-11: `docs/audits/fuzzing.md` (Echidna 2.3.3; distributor liability accounting, nine properties, 200,000 calls; router reserve arithmetic and deferred legs with a fuzzed feed, eight properties, 200,000 calls; no failure, seeds recorded) and `docs/audits/halmos.md` (eight checks proved over the Merkle claim binding and the reveal mapping, bounds stated). The harnesses are outside the Hardhat sources and never deploy. - Trail of Bits skills, 2026-09-11: `docs/audits/tob-2026-09-11/` holds the state-changing entry-point map and the nine-category code maturity scorecard produced with their published review skills, driven by an AI assistant. Not a Trail of Bits engagement. - Review of all of the above, 2026-09-15 (`docs/audits/STATUS-2026-09-15.md`): an additional in-house AI review (a different assistant pass, distinct from the Pashov-workflow run; not independent of us) read the reports against the code and found no new fund-loss issue and three weaknesses in the EVIDENCE, since fixed: the Halmos two-leaf check pruned the equal-amount case, the distributor harness never actually exercised duplicate leaves, and the router harness swallowed reverts and only bounded the crossing release. Both Echidna campaigns and Halmos were rerun on the fixed harnesses against the changed contracts (below); seeds, logs and image digests are in the addendum. The same review led to two CONTRACT changes, listed in section 5, which is why the phase 1 code baseline is the addendum commit, not the 2026-09-12 tag. - `contracts/AUDIT.md` (historical; its three-contract scope and test counts predate the current five) and `contracts/AUDIT-REVIEW-2026-09-01.md`: in-house contract review, findings and resolutions, accepted limitations noted; the Slither run there is from 2026-09-01 and was not repeated. - `.audit/REVIEW-2026-09-05.md` and `docs/SECURITY-REMEDIATION-2026-09-05.md`: application review that found five highs (token impersonation, deliberation fallback, legacy manual votes, former-holder proxies, admin bypass routes) and their repairs. Where that review's framing of the supervisors and quorum differs from `AGENTS.md`, `AGENTS.md` is current. - `docs/LAUNCH-AUDIT-2026-09-06.md` and `docs/LAUNCH-FIXES-2026-09-06.md`: second review (payout consistency across stores, commitment binding, mint network handling, runtime isolation, app authorization, site claims) and repairs. The rollout section there is historical; the migration it describes was performed on 2026-09-07. - `docs/AUDIT-BRIEF-READINESS-2026-09-09.md`: review of the first draft of this brief, `docs/AUDIT-RECHECK-70dbfb2.md`, and `docs/AUDIT-RECHECK-737c6b9.md`: historical rechecks of the fix snapshots. `docs/AUDIT-REMEDIATION-COMPLETE.md` records the final corrections and tests. Fixed in this snapshot: settlement and cycle recording require the funded root and total, the registry publication, the RPC's actual network, and transactions that carry this cycle's `CycleOpened` and `CyclePublished` events from the configured contracts with the record's values, with a confirmation policy, before any state changes; a Safe batch (receipt addressed to the Safe, both events in one hash) is accepted and an unrelated successful transaction is not (R1, C1, C2). The payout verifier fails closed with distinct verified, mismatch and incomplete outcomes and checks the network (R2, C2). Delegations and action approvals are version 2 messages naming domain, chain and collection (R3). Mandate terms are stated by the proposer, frozen into the document hash, and posted exactly; a document whose gate count does not fit the calendar is refused before its dissent window opens, at voting freeze (including re-votes), and again at post, never padded or truncated (C3). API openings resolve the latest calendar for that proposal inside the active deployment and refuse missing or unreadable calendars when terms are stated. JSON and PostgreSQL stores both preserve the terms across draft updates. - Rehearsal evidence on Sepolia, indexed in `docs/EVIDENCE.md`: eight governance cycles and two settlement rounds. Cycles 7 and 8 (2026-09-09) ran on the remediated code: version 2 commitments anchored under derived registry slots inside their windows, version 3 records published and verified against the registry, and in cycle 8 a manual ballot and two proxy approvals with holder signatures on the published record. Not exercised live: a token transfer between ballot and approval, and a vote with zero council ballots; both have test coverage. - The rules the code implements: `AGENTS.md` (current), `docs/CYCLE-MODEL.md` (economics; its description of the human steps predates the supervisors), `docs/STANDARDS.md` (strategy, not normative), `docs/VERIFY.md` (how a third party verifies). We do not consider our own reviews independent assurance; that is what this engagement is for. ## 5. Known issues and design decisions - assess them, do not skip them Listed so they are not reported as discoveries; severity, exploitability and the adequacy of the treatment are yours to judge. - Governance policies the council has not changed, coded as stated in `cycles/state-cycle-9.md`: killed mandates forfeit accrued fees; the idle council share falls to the treasury; a quorum-met tie fails rather than tables; gates are judged by their stated criteria, which the proposer now writes. - The Safe is not contract-constrained to the record (section 1). - Legacy records (cycles 1 to 6) were committed without frozen terms and are verifiable only with the limitations `docs/VERIFY.md` states. - The sign-in message carries a fixed chain id of 1; ballots, delegations and approvals bind the real chain separately. - **Changed 2026-09-15, please review the change:** the reveal now draws one offset PER TIER from the committed block hash (`councilOffset` modulo 100, `operatorOffset` modulo 1011, both derived from the same `keccak256(blockhash, address)` seed with a tier tag), and `metadataId` rotates each tier by its own offset. Before, one index modulo 1111 was reduced modulo both tier sizes, which conditional on a uniform residue gave some rotations two or three times the chance of others, and the "raw zero becomes one" guard added a further bias while still allowing the per-tier identity for 12 of 1110 indices. Each tier's rotation is now effectively uniform, with the negligible modulo bias of a 256-bit value over 100 or 1011 under the usual hash assumptions, the identity included as one outcome like any other; the raw `startingIndex` is kept and emitted for verification. The mapping remains a bijection per tier (Halmos, five checks, which establish the mapping's properties and say nothing about the randomness source) and the draw remains unpredictable before the commit under the same block-proposer and reveal-expiry assumptions as before. Rejection sampling to remove the residual bias was considered and not done. The 2026-09-14 Sepolia mint rehearsal ran on the previous mapping and is repeated on this code. - **Changed 2026-09-15, please review the change:** `release()` forwards a bounded `SEND_GAS` (100,000) to each destination, so a destination that burns gas instead of reverting is booked as owed like a rejecting one, rather than reverting the whole release; the Pashov-workflow review had reproduced two gas-burning destinations defeating the deferral under a 16.7M gas budget. `pushOwed` and `withdrawOwed` are unbounded on purpose: each pays one leg in its own transaction and can roll back nothing but itself, and a destination that needs more gas is paid that way. A Safe's receive path needs a few thousand gas; the tests cover a burner, two burners, a storing-and-requiring receiver, and a push to a burner failing alone. A receive test against the real Sepolia Safes is an outstanding acceptance check. - `Disorderly721.setRoyaltyReceiver` stays owner-settable with no freeze, decided 2026-09-11. The rate is a constant; the receiver is plumbing, and repointing it is the one fallback if the router has to be replaced after mint. A one-way freeze would remove that fallback. It is a standing power of the treasury Safe, disclosed on the mint page, and any use would be a published Safe transaction. Assess whether the disclosure and the two-signature gate are adequate treatment. - Development-dependency advisories exist in both toolchains; none in production installs. - The rehearsal droplet uses a public, load-balanced Sepolia RPC that has been observed to answer the same log query with the event once and nothing the next time, and to drop receipts. The recording rule fails closed on that (incomplete, retried next pass; contract state alone never records), so it costs time, not correctness. Production runs on a dedicated RPC endpoint; that is a launch prerequisite, listed in LAUNCH.md. - Per-cycle inference cost is recorded but not yet reported in the public close record; the site says so. ## 6. Build and test Node 22. No secrets are needed to run anything below; `.env` files and the Arweave wallet are excluded from the repository and must never be requested. npm ci && npm test # 31 server test files cd contracts && npm ci && npm test # 232 baseline; 259 with the supplement, local hardhat chain `npm test` discovers every `server/test-*.js`. The PostgreSQL file runs when `TEST_DATABASE_URL` points at a disposable database. A portable way, with Docker: docker run --rm -d --name disorderly-pg -e POSTGRES_PASSWORD=local-test-only -e POSTGRES_DB=audit_test -p 55439:5432 postgres:16 until docker exec disorderly-pg pg_isready -U postgres -d audit_test; do sleep 1; done TEST_DATABASE_URL=postgresql://postgres:local-test-only@127.0.0.1:55439/audit_test node server/test-postgres.js docker rm -f disorderly-pg CI runs both suites with the same PostgreSQL service on every push (`.github/workflows/test.yml`). ## 7. Deployment context for the review - Rehearsal chain: Sepolia. Two deployments, labelled in `ADDRESSES.md` section 4. The rehearsal Safe is 2 of 3 with the three Ledger keys listed there, verified on chain, and will not be reused. - Production: Ethereum mainnet. Three fresh Safes (treasury, reserve, ops) with the same hardware signers; the treasury Safe is the owner and proceeds destination of the collection, the distributor and the registry, and the deploy script assigns collection ownership to the deployer first with a two-step handover. The router is ownerless with three immutable destinations, a Chainlink ETH/USD feed, WETH, and a USD threshold seeded at deployment. A fresh database and an empty runtime; allowlist roots set by the Safe from lists derived in the final days before mint; reveal with the 100-block delay. - Constructor inputs decided late: the provenance hash (final art) and the Safe addresses. ## 8. What we ask for - Findings with severity, a concrete scenario, and the affected lines. - A distinction between contract findings and application findings. - A retest of fixes on the final tagged release, and a report we can publish in full. - Questions at any time to hello@disorderly.ai. ## 9. Package - Historical tags, kept as they are: `audit-2026-09-10` (`305c9e4`), `audit-2026-09-11` (`70dbfb2`), and `audit-2026-09-12` (`737c6b9`). Their readiness and recheck reports remain in this package. - Code baseline for this engagement: tag `audit-2026-09-12-remediation`, commit `1b1291126d26cdc974aac9e77807fcc53ca15981`. Resolve the source commit with `git rev-parse 'audit-2026-09-12-remediation^{commit}'` (the tag is annotated, so the bare tag name resolves to the tag object). Both suites and the PostgreSQL suite are covered by the completion report `docs/AUDIT-REMEDIATION-COMPLETE.md`. CI is configured to run on push; a local tag is not a claim of a completed remote CI run. - The supplementary Pashov-workflow package adds review documents and tests to that unchanged code baseline. These additions and this updated brief are not represented as files already in the immutable tag. Its contents and reproduction steps are listed in `docs/audits/pashov-2026-09-10/README.md`. - Reference for the handoff: tag `audit-2026-09-12-supplement`, the commit that adds the supplement and this brief on top of the code baseline. The five contracts are byte-identical between the two tags. The zip sent on request, `disorderly-audit-2026-09-12-supplement.zip`, is an export of this tag plus a `PACKAGE-MANIFEST.json` listing every file's sha256; its own sha256 is quoted in the invitation email. - **Evidence addendum, 2026-09-15, revision 3**: tag `audit-2026-09-15-addendum-r3` and the zip `disorderly-audit-2026-09-15-addendum-r3.zip` (sha256 in its `.sha256` file and in `docs/audits/STATUS-2026-09-15.md`). It adds everything that postdates the supplement tag: the static-analysis passes, both fuzzing harnesses with their campaign logs and seeds, the Halmos tests and log, the Trail of Bits skill outputs, the coverage report, the monitoring plan, the review-driven fixes and the two contract changes of 2026-09-15, plus a dated status note. Container images are recorded by digest. Revisions 1 and 2 are kept but superseded (r1 omitted the campaign logs; r2 selected the legacy reveal scheme by address alone and could overwrite a good state backup); the status note records both. The earlier zip and tags are untouched; the phase 1 review reads the contracts at the r3 commit, whose five contracts are byte-identical to r2's. - Access: read-only collaborators on the private GitHub repository, granted on signing. - Sent with the invitation: this brief and `docs/EVIDENCE.md`, with the scope in section 2 confirmed by both sides.