MANDATELaunch app →

Security & known limitations

Limitations disclosed here read as engineering maturity. The same limitations, found by a judge instead, would read as bugs. Everything below is a deliberate, considered tradeoff — not an oversight.

Threat model

The adversary of interest is the agent itself: an LLM-driven wallet holder with a strong incentive to escape its own mandate — widen its budget, extend its expiry, pay an unlisted recipient, or outlive a revocation. Every gate in this system exists to survive that adversary specifically, not a passive counterparty.

The Enforcer trust model

The Enforcer is a single off-chain service holding one signing key. It can only ever narrow a mandate or revoke it — it has no path to widen one, because syncMandate only ever mirrors what it reads from Sepolia. But it is a liveness dependency: if it stops running or is censored, MandateAnchor.assertSpend fails closed once the staleness window elapses, and every agent freezes. This is a deliberate choice — most systems that watch a chain fail open when the watcher goes down. A frozen agent is a worse demo beat than an unsupervised one; it is the correct security posture.

Allowlist inheritance, not subset-proving

A sub-agent’s allowlist root is copied verbatim from its parent’s, rather than proven as a subset on-chain. Proving one merkle root is a subset of another is expensive; inheriting the exact root makes widening it structurally impossible instead of merely checked. The cost is flexibility — a sub-agent can never be given a different (even narrower, hand-picked) allowlist without going through its parent’s own root.

Interest accrual is simple, on purpose

AgentTreasury.accrue computes simple interest and capitalizes it into principal on settlement. It is not compounding on a schedule, and it is the first thing this design would cut under time pressure — draw/repay alone already qualify the treasury for Arc’s payments and treasury categories without it.

ERC-8183 authorization, not independently verified

AgentTreasury.fundJob calls the reference Jobs contract’s fund(jobId, optParams) after approving USDC. Its exact authorization model — who may fund a given job, and whether the job’s budget must already be set by its creator — was confirmed from the contract’s verified selectors, not from an end-to-end call against the real deployment, which needs live credentials this build was deliberately kept independent of. Flagged here rather than assumed silently correct.

Centralization, disclosed rather than hidden

Every contract’s owner-gated function — MandateRegistrar’s org admin, MandateAnchor’s enforcer-change timelock, AgentTreasury’s utilisation and interest settings — is currently a single EOA, appropriate only for a testnet demo. A mainnet deployment requires a multisig from the first transaction, never a deployer key.