Security

Mirrored from SECURITY.md in the repository — edit that file, not this page.

AIOUM's contracts and runtimes are complete, tested, statically analyzed, and free of the bug classes that sank the original prototype. This page is the security record: how to report a vulnerability, what the test battery covers, the static-analysis triage, and the hardening every deployment carries.

Reporting a Vulnerability

We take security reports seriously and want to hear about anything you find.

Please do not open a public GitHub issue, pull request, or discussion for a security vulnerability. Public disclosure before a fix is available puts users at risk. Report it privately instead through a GitHub private security advisory: open a report at https://github.com/oum-dev/aioum/security/advisories/new. This keeps the discussion private between you and the maintainers until a fix is ready.

Please include enough detail to reproduce the issue: the affected contract or component and the commit/version, the impact you believe is possible, and a proof-of-concept or reproduction steps where you can provide them.

What to expect. We acknowledge a report within 5 business days and provide an initial assessment and a remediation plan within 30 days. We coordinate a disclosure timeline with you and credit you (if you wish) once a fix or mitigation is in place.

Testing

forge test → 226 tests passing across 11 suites (counts as of 2026-09-17):

  • AIOUMProtocol.t.sol (42) — happy paths + a regression test for every critical bug in the old code, plus per-service-split, dispute-timeout, bond-token-snapshot, reclaim-clock, accepted-asset-list, ZAI-not-a-payment-asset and burn-is-deflationary tests.
  • EconomicsUpgrade.t.sol (54 = the 42 protocol tests re-run under the new economics + 12) — per-asset exposure caps (escrowCapPerStake, capacity accounting across every session terminal path) and the proportional slash with stake snapshotted at raise.
  • Verifiability.t.sol (50 = 42 + 8) — commitRequired services cannot settle without an anchored results root; flag snapshotting per session.
  • CommitRevealArbitrator.t.sol (26) — dedicated arbitrator stake, commit-reveal voting, no-reveal penalties reaching the unbonding queue, reward pots that never spend stakes, the DAO escalation backstop.
  • ValidationQuorumArbitrator.t.sol (23) — stake-weighted quorum, double-vote/double-resolve guards, dispute-existence + vote-window checks, vote rejection on resolved/timed-out/nonexistent disputes.
  • VerifyingPaymaster.t.sol (13) — signature-gated sponsorship through the real EntryPoint: sponsored op, wrong signer, expiry, tamper, signer rotation.
  • AccountAbstraction.t.sol (8) — ERC-4337 end-to-end through EntryPoint.handleOps: session-key budgets, spend caps, expiry/revocation, bundling-safe opener queue.
  • DelegatedSlashing.t.sol (3) — pro-rata dilution, post-slash reward correctness, diluted undelegate.
  • Governance.t.sol (1) — full propose → vote → queue → execute lifecycle through the Timelock.
  • Fuzz.t.sol (StakingFuzzTest 2 + PaymentInvariantTest 4) — property tests: slashing conserves value & never underflows; reward accounting never inflates (accounted ≤ distributed) with shortfall bounded to rounding dust; plus StdInvariant campaigns over the payment system (per-asset solvency, no overpay, split conservation, active-session count).

The end-to-end off-chain flow (facilitator → node runtime → settle) runs on the local development network with on-chain balance deltas asserted to match the fee split exactly.

Static analysis (Slither)

slither . → 74 contracts analyzed, 27 results. Triage of the non-informational ones:

DetectorWhereVerdict
arbitrary-send-ethPaymentManager._payout, StakingManager._payoutAccepted. These send ETH to the rightful payee (session client / operator / treasury / staker), are nonReentrant, and follow checks-effects-interactions. The "arbitrary" address is the legitimate recipient resolved from session/stake state.
reentrancy-benignopenSessionWithAuthorization, openSessionWithPermit2, raiseDisputeAccepted. All three are nonReentrant; the flagged state writes happen after a guarded external pull. No exploit path.
timestamp comparisonsunbonding / inactivity / dispute windowsAccepted. Coarse time windows (days); miner timestamp drift is immaterial.
immutable suggestionsa few varsGas nit; left settable where governance may need to change them.

No high-severity exploitable findings.

Deployment hardening

  • Ownership. Every privileged function is onlyOwner with owner = the TimelockController (ARCHITECTURE.md §5). The local development network uses an EOA owner for convenience.
  • Assets. A production deployment uses the canonical Permit2 and real stablecoins (PERMIT2_ADDRESS / USDC_ADDRESS); the local development network deploys mock USDC and Permit2 and mints freely.
  • Paymaster. A production deployment ships the signature-gated AIOUMVerifyingPaymaster, left unfunded until operators fund it deliberately; verify-deploy fails closed on a funded accept-everything paymaster.
  • Vouchers. The node runtime persists the latest voucher per session to disk and reloads it on boot, so a restart mid-session still settles without the client re-sending the last voucher.
  • Facilitator. Non-custodial and hardened: EIP-3009 validation, asset allowlist, amount cap, per-IP rate-limit, nonce replay guard, optional bearer auth. Payment authorizations name the node as recipient, so the facilitator never holds funds, and operators can run several for liveness.
  • Invariants. Foundry fuzz and StdInvariant campaigns (Fuzz.t.sol) cover staking accounting and payment escrow: per-asset solvency, no overpay, split conservation, active-session count.

Bug classes from the original prototype — all closed

See ARCHITECTURE.md §9. Every drain-level bug (public createChannel, public distributeRewards, missing slashing, dead signature code, node-settable capacity, reward double-count, single-EOA admin, CEI violations) has a corresponding fix and, where applicable, a regression test that fails against the old behavior.

Dispute liveness is covered by DisputeManager.resolveStale: after disputeTimeout anyone can force-close a stalled dispute (refund the client + return the bond, no slash), so escrowed funds can't be held hostage by an unresponsive arbitrator.