Security

Test suite layout, invariants, and the adapter's structural defenses.

Test suite layout#

The repository ships with a layered Foundry test suite:

test/
├── Adapter8004.t.sol                 # original unit tests
└── security/
    ├── Adapter8004.security.t.sol    # coverage-gap tests
    ├── Adapter8004.adversarial.t.sol # hostile tokens + registries
    ├── Adapter8004.fuzz.t.sol        # property tests
    └── mocks/
        ├── MaliciousERC721.sol
        ├── MaliciousERC1155.sol
        ├── MaliciousERC6909.sol
        ├── RevertingToken.sol
        └── OverflowRegistry.sol

Run everything:

forge test
forge coverage --no-match-coverage "test/|script/"

Expected result: 100% line, statement, branch, and function coverage on src/Adapter8004.sol.

Core invariants#

The fuzz suite exercises these under 256 random inputs each:

  1. Register lookup round-trip — a successful register always makes agentIdForBinding return the same agentId with exists = true.
  2. Controller follows owner (ERC-721) — transferring the bound token moves control atomically with no extra call to the adapter.
  3. Shared control (ERC-1155 / ERC-6909) — every current holder passes isController, every non-holder fails.
  4. Non-controllers are always rejected — random attacker addresses cannot call any controller-gated function.
  5. Admin gating — only the owner can swap the registry or upgrade.
  6. agentId encoding — the internal agentId + 1 storage never confuses slot 0 with "unset" for any (standard, contract, id) tuple.

Adversarial scenarios#

The adversarial suite covers hostile behavior from both sides of the adapter:

Malformed token contracts#

  • Reverting tokens — every revert path propagates; no stale binding is left behind.
  • EOA as tokenContract — Solidity's extcodesize check aborts the call before anything else happens.

Attacker-controlled token contracts#

  • Forced ownerOf / balanceOf — documents the trust boundary. A malicious token can name whoever it wants as the controller. This is the known trust assumption of the adapter.
  • Control flips between calls — whoever the token currently names is the controller. The adapter reads fresh on every gated call.

View-time reentrancy#

The adapter's _hasBindingControl is internal view, and the ERC-721 / ERC-1155 / ERC-6909 interface reads are declared view. Solidity compiles those calls as STATICCALL, so any attempt by a hostile token to write state or make a non-static CALL from inside ownerOf / balanceOf kills the whole transaction.

This is a structural defense that depends on never relaxing the view declarations in the adapter. Any future refactor that downgrades them from view to non-view should be treated as a security-critical change.

Hostile registry#

  • agentId + 1 overflow — a stub registry returning type(uint256).max from register makes the adapter revert with an arithmetic error instead of silently wrapping to zero.
  • Registry swap hazard — old bindings become read-only artifacts and writes to them revert at the new registry's auth check. This is documented, not prevented.

Admin self-footgun#

  • Owner renouncementrenounceOwnership() permanently locks both setIdentityRegistry and upgradeToAndCall. Deliberate, but worth documenting as a point of no return.

Audit notes#

  • Trust boundary: the adapter trusts the bound token contract to report honest ownerOf / balanceOf. Only bind to token contracts you trust.
  • Shared control is intentional: ERC-1155 / ERC-6909 can produce multiple equally-valid controllers. This is a design choice, not a bug.
  • EIP-712 owner field is the adapter address, not the token holder. Off-chain signers must sign against the adapter address.
  • Upgrade power is concentrated in a single owner. Renounce after setup if that's unacceptable for your threat model.
  • No ReentrancyGuard is needed because the adapter's external calls to the bound token happen through view interface methods, which Solidity compiles as STATICCALL. The external call to the registry happens after all local state writes, so even if the registry reentered the adapter, it would see consistent state.

Reporting issues#

Security reports should go to the adapter maintainers via the channels listed in the repository's SECURITY.md. Do not file public issues for vulnerabilities.