Skip to content

[security] Block typed-data signing surface from shipping until Inv #1b + #2b land #453

Description

@szhygulin

Summary

Adversarial scripts 126 (Permit2 batch), 127 (CowSwap order), 128 (EIP-2612 permit) currently fail to land only because vaultpilot-mcp deliberately excludes typed-data signing today (the WC namespace + Safe.approveHash design). This is defense-by-gap, not defense-by-design.

The moment a prepare_eip2612_permit / sign_typed_data_v4 / prepare_cowswap_order / prepare_permit2_* ships without paired Invariant #1b and Invariant #2b, a rogue MCP swaps spender inside Permit{...} and the existing skill catches nothing:

  • Hash recompute alone passes tautologically (the digest is over the tampered tree).
  • Invariant v0.2.3: document approval→wait→main-tx pattern to all agents #11's permit(...) reference is for the on-chain submission selector, not the upstream typed-data signing where the attack lands.
  • The asymmetry is enormous: ONE permit signature → perpetual transfer authority for the lifetime of the deadline. Permit2 batch with 5-year expiration on USDT (script 126) is the worst case.

Proposal — block coordinated release

Treat shipping the typed-data tool surface and shipping #1b+#2b as a single coordinated release.

Invariant #1b (typed-data tree decode):

  1. Decode the typed-data tree locally: domain, types, primaryType, message.
  2. Walk types, locate every address-typed field (spender, to, receiver, verifyingContract), surface each in CHECKS PERFORMED with bold + inline-code markup.
  3. Surface deadline / validTo / expiration with delta-from-now in human-readable units; flag if > 90 days.
  4. Pin verifyingContract against curated map (Permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3, USDC permit domain, CowSwap settlement, etc.); refuse on mismatch.
  5. If primaryType{Permit, PermitSingle, PermitBatch, Order}, treat as approval-class — apply Inv v0.2.3: document approval→wait→main-tx pattern to all agents #11 unlimited / long-lived rules per entry.

Invariant #2b (digest recompute): independently recompute keccak256("\x19\x01" || domainSeparator || hashStruct(message)) from the decoded tree and match against MCP-reported digest.

Hard precondition: Ledger device must clear-sign the typed-data type for the target token. If the device blind-signs the digest, the agent must refuse.

Source

Adversarial smoke-test, 2026-04-28. Scripts 126, 127, 128.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-requestFiled by an AI agent via the request_capability toolbugSomething isn't workingbug_reportCategory: reports a bug surfaced via an agent's attempt at a capability

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions