← back to build & deploy plan
Deep Security Audit · Claude + Codex convergent · pre-deploy

RMT Contracts — Deep Security Audit 9 in-house RMT contracts (1,266 LOC) audited by independent Claude + Codex lenses, every finding adversarially re-verified against source. Shyft core excluded (already audited).

0 CRITICAL 0 HIGH 5 MEDIUM 6 LOW / INFO ✓ no third-party-exploitable defect
Verdict: the RMT layer is fundamentally sound — access control, reentrancy guards, SafeERC20, delta/cooldown/duplicate caps and checked math are all correct. Zero critical/high exploitable bugs. Every confirmed item is a deployment-ordering, single-key-trust, or centralization issue — fixable by deploy sequencing + multisig, none requiring contract rewrites except the (already-required) Fee Router. Claude and Codex independently converged on the top items.
M

Confirmed — MEDIUM (decide before deploy / mainnet)

MEDIUM

M1 — Citation attribution can be spoofed before the engine is wired

Codex SEV-001
ShyftGatedResolver.sol · attest() L160–164

The attribution check ("only the anchor that registered fromBot may cite for it") is skipped while reputationEngine == address(0). In the window after the resolver is live but before setReputationEngine() is called, any verified Trust Anchor can record a citation with fromBot = someone else's bot.

Action (deploy-ordering): the deploy script must call setReputationEngine() before the resolver can receive any attestation, and the EAS schema/resolver should not be wired into EAS until then. Treat as a hard sequencing invariant + verify post-deploy.
MEDIUM

M2 — Batch citation path skips the real gates

Codex SEV-002
ShyftGatedResolver.sol · batchRecordCitations() L206–235

batchRecordCitations (callable by batchAnchor or owner) does not enforce toBot Shyft attestation, shared trust channel, attribution, cooldown, or fee — it only checks fromBot is anchor-verified. A compromised batchAnchor key can mint citation counts to arbitrary targets, free.

Action: batchAnchor must be a trusted multisig/oracle key (treat as privileged). For production, add the channel + target-registration checks to the batch path so it matches attest().
MEDIUM

M3 — A compromised oracle can pre-warm / ladder scores

Codex SEV-003 + Claude PRO-1
PageRankOracle.sol · submitScores() L85–108

Two convergent angles: the oracle can (a) pre-score an unregistered address over epochs so it enters with a high score the moment it self-registers; and (b) walk a score past the initial cap to 10000 via repeated sub-maxDelta steps (the delta cap bounds per-epoch, not cumulative). Already documented testnet-accepted (S-03, single-oracle trust).

Action (mainnet gate): decentralize the oracle (multi-operator / 3-of-5) and bound cumulative score before mainnet. Fine for testnet as documented.
MEDIUM

M4 — Self-registration grants an unearned reputation floor

Claude RE-1
ReputationEngine.sol · selfRegisterBot() → PageRankOracle.getCompositeScore()

Any address can selfRegisterBot() (no Trust Anchor) and immediately reports a 500 bp composite "seed" score it never earned (verified bots get 2000). Unlimited Sybils each carry a starter reputation — if getCompositeScore feeds gating.

Action: confirm whether getCompositeScore (vs getReputation, which returns 0 for unscored) is used for any gating. If yes, gate the seed floor behind verification; if no, downgrade to info.
MEDIUM

M5 — ERC8004Bridge assumes a registry that returns zero (not reverts)

Claude B-1
ERC8004Bridge.sol · bridgeAgent() L49–50

It treats registry.ownerOf(unknownId) == address(0) as "not registered." Canonical ERC-721 ownerOf reverts for nonexistent tokens — against such a registry the AgentNotRegistered path breaks (opaque revert) and the agentId-0 safety assumption weakens. Only the in-repo mock returns zero.

Action (pre-deploy): verify the actual Stable ERC-8004 registry's ownerOf semantics; adapt the bridge (try/catch) if it reverts.
L

Confirmed — LOW / INFO (harden before production)

LOW

Centralization — single-step Ownable, no timelock, across all contracts

Claude + Codex

Owners control fees, oracle binding, channel index, fee withdrawal, pause. The MachineConsentHelper owner is the highest-value key (controls the Shyft consent-signer mapping). RMTToken has uncapped mint with deployer holding admin+minter.

Action: move owners + MINTER to a multisig, add Ownable2Step + timelock before production; move MINTER_ROLE to the fee-distribution contract.
LOW

Config foot-guns & operational notes

MAX_EPOCH_GAP=0 is immutable and permanently bricks score reads — validate at deploy.  • citation fee requires attester pre-approval or the attestation reverts (fail-closed, fine).  • registerExisting can leave non-contiguous domainIds (owner-only, read paths hole-safe).  • ERC8004 links can't be unlinked (no key rotation) + a level-1 floor blocks downgrades.

Action: add a constructor bound on MAX_EPOCH_GAP; document the rest; decide on an unlink path for key rotation.

Pre-deploy / pre-mainnet checklist (derived from findings)

PRE-DEPLOY
Wire setReputationEngine() before the resolver is usable (M1) — bake the ordering + a post-deploy assertion into the deploy script.
PRE-DEPLOY
Verify the Stable ERC-8004 registry's ownerOf returns zero (not reverts) for unknown agents (M5); adapt the bridge if needed.
PRE-DEPLOY
Set batchAnchor to a trusted multisig/oracle and validate MAX_EPOCH_GAP > 0 (M2, foot-gun).
PRE-DEPLOY
Confirm whether getCompositeScore gates anything (M4) — decide the seed-floor policy.
PRE-MAINNET
Decentralize the oracle (multi-operator, cumulative-score bound) (M3); multisig + timelock all owners + MINTER (centralization).
×

Verified false-positives (rigor — looked suspicious, proven safe)

MCH self-key  auxSigners[address(this)] is intentional (MCH IS the anchor) — confirmed by its tests.
MCH ERC-165 selector  non-standard single-selector ID is the exact one Shyft's Attestations.sol probes — intentional.
Factory ownership window  deploy→wire→register→transfer is atomic in one tx; OZ v5 transfer is single-step. No stranded state.
totalRegisteredBots underflow  decrement only after a confirmed prior increment — never underflows.
RMTToken burn  burn is self-only (_burn(msg.sender)); no burnFrom/allowance drain.
Duplicate bot in batch  rejected via per-epoch check; role separation onlyOracle/onlyMultisig is correct.
Method. 4 independent passes (3 Claude security lenses by contract group + 1 Codex lens on the fee/gating/oracle trio), each finding adversarially re-verified against source with file:line proof; false-positives dismissed with proof. This is the first-pass deep audit feeding the FSM AUDIT phase.
Convergence. Claude + Codex independently agreed on the oracle-trust cluster (M3); Codex sharpened the attribution-window (M1) and batch-path (M2) findings.
Next. (1) fold M1/M2/M5 into the deploy script + Fee Router design; (2) formal Grok + Codex + Gemini signed convergence panel in the FSM AUDIT phase; (3) dedicated red-team session for adversarial guarantees. Scope: 9 RMT contracts in antilles-v2; Shyft core excluded (already audited).