Skip to content

feat: deny-wins semantics + any-target + calldata guard for freedom gate#596

Closed
gomesalexandre wants to merge 2 commits into
mainfrom
feat/freedom-deny-support
Closed

feat: deny-wins semantics + any-target + calldata guard for freedom gate#596
gomesalexandre wants to merge 2 commits into
mainfrom
feat/freedom-deny-support

Conversation

@gomesalexandre

Copy link
Copy Markdown
Contributor

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.
  • If any DENY rule's constraints match the tx, evaluation returns a denied error immediately, regardless of any ALLOW rules.
  • Existing ALLOW-only policies are unaffected (no DENY rules = no-op first pass).
  • Implementation uses 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_UNSPECIFIED now means "no recipient restriction".
  • Used by the freedom policy native ETH transfer rule (any recipient, amount-capped).

Calldata guard in native transfer (engine/evm/evm.go)

  • Rejects native-transfer rule evaluation if the tx has calldata.
  • Without this, a contract call sending 0 wei silently passes the native-transfer amount cap.

tests

New TestDenyWins in engine/engine_test.go: builds a USDC approve(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

gomesalexandre and others added 2 commits June 30, 2026 15:40
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
@gomesalexandre

Copy link
Copy Markdown
Contributor Author

Heads-up: locally the engine tests hit a sonic/Go-1.26 linkname build error (invalid reference to encoding/json.unquoteBytes) across all packages — looks like a Go-version vs sonic-version detail, not the DENY change. Worth confirming the CI Go version builds clean (the consuming verifier#583 bumped sonic to v1.15.2 for Go 1.26; the recipes go.mod may want the same). The DENY-wins + calldata-guard logic is exercised end-to-end + green via verifier#583's gate tests.

@gomesalexandre

Copy link
Copy Markdown
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant