The published line and the local line of this repository had no common ancestor: the public one carried the hygiene pass (no host names, no internal paths), the local one carried a month of corrections that never shipped. This commit ports the local work onto the public line, keeping the public hygiene wording wherever the two touched the same sentence, and keeping the public version of AERE-CROSS-CLIENT-DETERMINISM.md entirely. Carried: LICENSE/LICENSING corrections, VERIFY-POLICY.md, CITATIONS-UNRESOLVED.md remeasured 2026-08-11, the 'audited' adjective removed from next to Bouncy Castle, citation paths rewritten to published form, AIP-8, the QA consolidation report, the second EIP validation pass, fork-height corrections, the AereSink / threshold-factory correction, the forge test floor, and the architecture-map updates.
6.5 KiB
6.5 KiB
Aere Network: adversarial-review fixes (pre-deploy hardening)
Source review: aerenew/docs/AERE-SECURITY-REVIEW-NEW-CONTRACTS.md
Repo: aerenew/contracts (solc 0.8.23, OpenZeppelin v4.9.x). All contracts founder-gated, not deployed.
Guard pattern for L1 copied from aere-contracts/contracts/pqfinality/AereFinalityCertificateVerifier.sol (gateway.code.length == 0 revert).
Per-finding status
M1 (MEDIUM) FIXED, with proof
File: aere-contracts/contracts/erc8004/AereReputationRegistry8004.sol
- Made the adapter
Ownable(OZ v4,@openzeppelin/contracts/access/Ownable.sol, matching the repo). - Added
mapping(address => bool) public isAuthorizedFeedbackAuthor, owner functionsaddFeedbackAuthor/removeFeedbackAuthor(withFeedbackAuthorSetevent and a zero-address guard on add), and a new errorNotAuthorizedFeedbackAuthor(address). - Gated
giveFeedback: first line now revertsNotAuthorizedFeedbackAuthorunlessisAuthorizedFeedbackAuthor[msg.sender]. The rest of the flow (agent-registered, delta clamp, adapter-is-attestor) is unchanged. - Design choice (documented in NatSpec): owner-curated author allowlist, mirroring the live AereAIReputation curated-attestor model. Scope is authorization-only: the owner cannot set a reputation value, cannot exceed the
{-1,0,+1}delta clamp every author is held to, and moves no funds. No re-entrancy, no fund/reputation-value backdoor. Read path (getScore,getStats,liveKeyOf,canWriteThrough) untouched.
L1 (LOW) FIXED (x3), with proof
Added the sibling's codeless-gateway guard to each verify path. New error VerifierHasNoCode() in each.
aere-contracts/contracts/mpc/AerePQAggregateVerifier.solconstructor: reverts ifgateway.code.length == 0(after the existingZeroGatewaycheck).aere-contracts/contracts/depin/AereComputeMarketV3.solconstructor: reverts ifzkVerifier.code.length == 0(after the existingZeroAddresscheck).aere-contracts/contracts/compliance/AereBitstringStatusList.solconstructor: reverts ifnonRevocationVerifier_ != address(0) && nonRevocationVerifier_.code.length == 0. The zero address is still allowed (keeps the zk non-revocation path fail-closed OFF until configured), which preserves existing behavior. Verifier is immutable, so the constructor is the correct place.
L2 (LOW) FIXED, with proof
File: aere-contracts/contracts/agentic/AereVectorStore.sol
- Bound each paid-query receipt to an intended provider. Added
address providerto theQueryReceiptstruct. paidQuerynow takesaddress provider(validated non-zero via existingZeroAddress) and records it in the receipt. Signature is nowpaidQuery(storeId, queryHash, provider, agentId, amount, nonce, deadline, authSig). The settlement/forwarding flow (AERE402 rail, fee to sink, delta-measured forward to recipient) is unchanged;receiptIdformula unchanged.attestRetrievalpriced branch now reverts new errorNotReceiptProvider()unlessmsg.sender == receipt.provider, so a third party (or the payer) cannot front-run the honest provider and burn the receipt with a junk resultRoot.getReceiptview extended to also returnprovider.
L3 (LOW) DOCUMENTED (accepted risk), not code-changed
File: aere-contracts/contracts/erc8004/AereAP2MandateVerifier.sol
- Chose DOCUMENT over CODE-CHANGE, per the task's guidance, because the two candidate code fixes are unsuitable: (a) a per-authorization nonce would break the intended standing-mandate ("sign once, spend repeatedly up to the cap") semantics that the design and tests rely on; (b) "bind consumption to the executor" is already the built-in
SETTLEMENT_EXECUTORgate. - Strengthened the contract-level and
authorizeSpendNatSpec: the open accounting path (SETTLEMENT_EXECUTOR == address(0)) is verification/test-only and MUST NOT be used in production; production MUST setSETTLEMENT_EXECUTORto the AERE402 facilitator so a mandate's cap is only ever consumed alongside a real settlement. No funds at risk (this contract holds none); the vector is cap-exhaustion griefing only. - The existing test "enforces the settlement-executor gate when one is configured" already proves the mitigation works (a non-executor is rejected, the configured executor succeeds). No new test needed.
Tests added / changed
aere-contracts/test/erc8004-adapters.test.js:deployRepnow authorizesotheras a feedback author (both branches). Two pre-existing tests that relied on the ungatedgiveFeedbackwere updated to call as the authorizedother(legitimate behavior change from the M1 gate). Added new test "gates giveFeedback to owner-authorized feedback authors (M1)": unauthorized caller revertsNotAuthorizedFeedbackAuthorand writes nothing, owner authorizes -> same call succeeds and moves the live score to 10, owner revokes -> re-blocked, and only the owner may manage the allowlist.aere-contracts/test/AerePQAggregate.test.js: added "rejects a code-less SP1 gateway at construction (L1 fail-open guard)" (EOA gateway revertsVerifierHasNoCode, zero revertsZeroGateway, real mock deploys).aere-contracts/test/AereComputeMarketV3.test.js: added "construction guards" > "rejects a code-less ZK verifier at construction (L1)" (EOA revertsVerifierHasNoCode, zero revertsZeroAddress, real mock deploys).aere-contracts/test/aere-pq-screen.test.js: added "rejects a non-zero code-less non-revocation verifier at construction, but allows zero (L1)" (EOA revertsVerifierHasNoCode; zero deploys andnonRevocationConfigured()==false; real mock deploys).aere-contracts/test/aere-vector-store.test.js: the twopaidQuerycalls updated to passprovider.address; the paid-query test extended to assert the receipt is bound to the provider and that a third party (operator) and even the payer (consumer) are rejectedNotReceiptProviderwith the receipt left unconsumed, then the bound provider consumes it successfully (L2 proof).
Real re-run results (npx hardhat test , from aerenew/contracts)
- aere-contracts/test/erc8004-adapters.test.js .... 19 passing
- aere-contracts/test/aere-vector-store.test.js ... 8 passing
- aere-contracts/test/AerePQAggregate.test.js ..... 18 passing
- aere-contracts/test/AereComputeMarketV3.test.js . 14 passing
- aere-contracts/test/aere-pq-screen.test.js ...... 13 passing
npx hardhat compile: green (compiled successfully, then "Nothing to compile" on re-run).
Notes
- No contracts outside the findings were touched. No existing guard was weakened. The L1 zero-address behavior for AereBitstringStatusList was intentionally preserved. Prose contains no em-dashes or "--".