Security Model
What Policies Do — And Don't — Protect Against
Policies protect against:
- Unrestricted spending. SpendingLimitHook caps per-token amounts over configurable time windows.
- Calling unauthorized contracts. AllowlistHook restricts which addresses and function selectors the wallet can call.
- Complete fund drain. Policies are enforced on-chain — a compromised agent or modified SDK cannot bypass them.
- Agent key compromise. Session keys limit scope to specific contracts/selectors and expire automatically.
- Runaway agents. EmergencyPauseHook provides an instant circuit breaker to freeze all wallet activity.
Policies do not protect against:
- Protocol-level exploits. If a contract your agent interacts with has a vulnerability, policies cannot prevent exploitation through normal-looking calls.
- All forms of value movement. Token wrapping, flash loans, and delegate calls are not tracked by spending limits.
- Price manipulation. Spending limits are denominated in token amounts, not USD. A token's price can change within a window.
- Owner key compromise. The owner has full control by design. If the owner key is compromised, policies can be removed.
- Replacing audits. Custom hook contracts need their own security review. The SDK does not audit user-deployed hooks.
This is an honest accounting. SmartAgentKit significantly reduces the attack surface for AI agent wallets, but it is not a substitute for defense in depth, code audits, and operational security.
Threat Model
SmartAgentKit is designed for a specific threat: a compromised or malfunctioning AI agent that has access to a wallet. The policy system limits what damage the agent can do.
Threats Mitigated
- Unlimited spending: SpendingLimitHook caps per-token amounts over configurable time windows
- Calling unauthorized contracts: AllowlistHook restricts callable targets and function selectors
- Complete fund drain: Policies are enforced on-chain, not in the SDK -- the agent cannot bypass them
- Agent key compromise: Session keys limit scope to specific contracts/selectors and expire automatically
Threats NOT Mitigated (Out of Scope)
- Owner key compromise: The owner has full control by design. Protect the owner key.
- Smart contract bugs in Safe/Safe7579/EntryPoint: We rely on their audits (Ackee Blockchain for Safe7579, OpenZeppelin for Safe)
- Chain-level attacks: Reorgs, censorship, and validator collusion are outside our scope
- Social engineering: If the wallet owner is tricked into removing policies, the system cannot help
On-Chain Enforcement
All policies are ERC-7579 hooks executed during every UserOperation:
- HookMultiPlexer routes to ALL installed hooks -- you cannot skip individual hooks
- If ANY hook reverts, the entire UserOp fails
- Hooks execute before the target call (
preCheck) and after it (postCheck) - This enforcement happens at the EVM level, not in the SDK -- a malicious SDK fork cannot bypass it
Hook Execution Flow
UserOp submitted
|
EntryPoint validates
|
Safe7579 adapter calls HookMultiPlexer.preCheck()
|
HookMultiPlexer calls each sub-hook:
|-- SpendingLimitHook.preCheck() -- checks spending within window
|-- AllowlistHook.preCheck() -- checks target + selector allowed
|-- EmergencyPauseHook.preCheck() -- checks not paused
|
If ALL pass --> execute the call
|
HookMultiPlexer calls each sub-hook postCheck()
|
UserOp succeedsModule Management Protection
The AllowlistHook's protectedAddresses feature prevents the account from calling module management functions on itself. This means:
- The agent cannot uninstall its own policy hooks
- The agent cannot modify the HookMultiPlexer configuration
- Only the owner (via a direct Safe transaction, not through the agent's UserOp path) can modify modules
Self-Call Blocking
Accounts cannot call themselves through the hook system. This prevents an agent from bypassing policies by calling the account's own execute() function recursively.
Guardian Model
The EmergencyPauseHook uses a separate guardian key:
- Instant pause: Guardian can pause the wallet with a direct contract call, not a UserOp
- Cannot be blocked: Since pause is a direct call, it is not subject to policy checks
- Cooldown: 1-hour cooldown between pauses prevents griefing by a compromised guardian
- Auto-unpause: Optionally configured timeout after which the wallet automatically unpauses
Known Limitations
Token wrapping: SpendingLimitHook does not track WETH wrap/unwrap operations. An agent could wrap ETH to WETH (or vice versa) to partially circumvent token-specific limits.
Flash loans: Not detected by spending limits. An agent could use flash loans to amplify its effective spending within a single transaction.
Delegate calls: Hook checks do not cover delegate call patterns. ERC-7579 accounts should disable delegate call if this is a concern.
Price manipulation: Spending limits are denominated in token amounts, not USD value. A token's price could change significantly within a spending window.
In-memory sessions: Session metadata does not persist across SDK restarts. The on-chain session remains valid, but the SDK loses its local record of active sessions.
ERC-20 detection: SpendingLimitHook detects
transfer()andapprove()calls by selector matching. Non-standard ERC-20 implementations or calls through proxy patterns may not be detected.