feat: deny-wins semantics + any-target + calldata guard for freedom gate#596
feat: deny-wins semantics + any-target + calldata guard for freedom gate#596gomesalexandre wants to merge 2 commits into
Conversation
DENY rules now win unconditionally: the engine scans EFFECT_DENY rules before the ALLOW scan and returns a denied error on first match. Existing ALLOW-only policies are unaffected (no DENY rules = no-op first pass). Also adds TARGET_TYPE_UNSPECIFIED as "any recipient" in the EVM engine's assertTarget. Previously UNSPECIFIED fell through to an error; now it means "no target restriction", letting freedom-policy native transfers omit the recipient without needing a hardcoded address. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WFkUyPvR2WuDfThGiVYLke
assertArgsNative now returns an error if the tx has non-empty calldata. Without this guard, a contract call sending 0 wei (e.g. an ERC-20 transfer) would silently match the native-transfer amount cap (0 wei <= any MAX cap) and pass policy evaluation. A native-asset transfer is defined as a value-only tx with empty data; anything with calldata must route through the ABI/ERC-20 evaluation path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WFkUyPvR2WuDfThGiVYLke
|
Heads-up: locally the engine tests hit a |
|
Closing: this added DENY-wins / TARGET_TYPE_UNSPECIFIED / the assertArgsNative calldata guard specifically to feed the freedom MPC co-signer policy gate (verifier #583), which is now CLOSED — the live stack signs freedom txs on-device via the existing 2-of-2 (/vault/sign), not through a server-side co-signer. With no consumer, this rides along as dead weight. Reopenable if the recipes engine later wants DENY-wins as a standalone primitive. |
Prereq for the freedom co-signer policy gate in vultisig/verifier.
Three additions to the recipes engine, all backward-compatible:
what
DENY-wins semantics (
engine/engine.go)Evaluate()now scans EFFECT_DENY rules before the ALLOW scan.proto.Clone+ effect override so DENY constraints run through the same chain-engine evaluation path as ALLOW — no duplicated logic.Any-target support (
engine/evm/evm.go)TARGET_TYPE_UNSPECIFIEDnow means "no recipient restriction".Calldata guard in native transfer (
engine/evm/evm.go)tests
New
TestDenyWinsinengine/engine_test.go: builds a USDCapprove(spender, uint256.max)tx and asserts a policy with DENY(amount=uint256.max) + ALLOW(MAX=uint256.max) returns a "denied" error.risk
Low — all additive, existing ALLOW-only policies are unaffected.
🤖 Agent-generated