Skip to content

fix(abstract-eth): validate defi calldata - #9583

Merged
venkateshv1266 merged 1 commit into
masterfrom
defi-614-calldata-validation
Aug 28, 2026
Merged

fix(abstract-eth): validate defi calldata#9583
venkateshv1266 merged 1 commit into
masterfrom
defi-614-calldata-validation

Conversation

@ralph-bitgo

@ralph-bitgo ralph-bitgo Bot commented Aug 27, 2026

Copy link
Copy Markdown

What

Add fail-closed calldata validation for defiApprove, defiDeposit, and
defiWithdraw in AbstractEthLikeNewCoins.verifyTssTransaction (mirrors the
established WCN-495/security-fix ladder pattern in the same function).

For each defi intent, the client now decodes the server-built txPrebuild.txHex
calldata and compares it against user intent before signing:

  • defiApprove (approve(address,uint256) = 0x095ea7b3): selector match;
    decoded amount == defiParams.amount.
  • defiDeposit (deposit(uint256,address) = 0x6e553f65): selector match;
    decoded receiver == wallet baseAddress; assets == defiParams.amount.
  • defiWithdraw (redeem(uint256,address,address) = 0xba087652): selector
    match; decoded receiver == baseAddress and owner == baseAddress;
    decoded shares == defiParams.amount. For withdrawals defiParams.amount
    is the vault share amount to redeem (base units of the vault share token —
    see withdrawFromVault / the defi-withdraw intent mapping to
    shareTokenAmount), so it is directly comparable to redeem's first arg.

Any mismatch, missing defiParams/baseAddress, wrong selector, or
undecodable/short calldata throws TxIntentMismatchRecipientError — the client
never returns true for a defi-typed tx without a successful decode + match.

Why

TOB-BITGOEDMPC-2 (Trail of Bits High, parent CECHO-1905):
verifyTssTransaction trusted the server-supplied txParams.type and returned
true for defi types with no calldata decoded, so a compromised BitGo server
could substitute attacker calldata (e.g. deposit(amt, attacker) redirecting
funds, or approve(attacker, MAX_UINT256)) and the client would MPC-sign it.

What is NOT done here (and why)

  • defiApprove spender is not client-validated. The client has no copy of
    the vault allowlist, so it cannot independently confirm the spender is the
    intended vault deposit target. This is not a gap to fill by duplicating
    config
    — spender and vault-contract allowlisting is already enforced
    server-side by Wallet Platform (verifyDefiCalldata /
    validateDefiAllowlist in
    bitgo-microservices/.../abstractEthLikeCommon.ts). Maintaining vault configs
    in the SDK as a second source of truth would be redundant and drift-prone; the
    authoritative allowlist stays in WP. The client-side selector + amount checks
    still catch approve(attacker, MAX_UINT256) (amount mismatch) and
    arbitrary-action substitution (selector mismatch).
  • txJson.to (the token/vault contract) is not client-validated, for the
    same reason — the client has no vault config to compare it against, and WP
    already allowlists it.
  • Intent is not derived purely from parsed calldata. The branch still enters
    on txParams.type, then validates the selector is consistent with that type.
    This is deliberate: the approve selector (0x095ea7b3) is shared between
    tokenApproval and defiApprove, and transfer (0xa9059cbb) is shared
    between transfer and transferToken, so the selector alone cannot
    disambiguate the user intent. Full calldata-derived intent is the parent
    redesign's scope (see below).

Deferred to parent CECHO-1905

  • The broader redesign of verifyTssTransaction to derive intent from parsed
    calldata (a pluggable per-type decoder table) rather than trusting
    txParams.type. This PR's defi branches are sibling if blocks outside the
    recipient gate, so they compose with that ladder and can move verbatim into a
    decoder-table entry — a mechanical refactor, not a redesign.
  • If independent client-side spender/vault-contract verification is later
    desired (defense-in-depth beyond WP), CECHO-1905 can add an SDK→defi-service
    vault-config fetch and compare spender / txJson.to against it. That is a
    new SDK network dependency and is out of scope for a calldata-validation fix.

This PR follows the WCN-495/security-fix pattern (same file, same function,
same per-type calldata-decode-and-fail-closed ladder), so the two branches merge
cleanly.

Test plan

  • modules/abstract-ethyarn lint + yarn unit-test (182 passing)
  • modules/sdk-coin-ethyarn lint + yarn unit-test (439 passing),
    including:
    • happy paths for defiApprove, defiDeposit, defiWithdraw (real calldata)
    • attacker calldata: approve(attacker, MAX_UINT256),
      deposit(amt, attacker), redeem(shares, attacker, attacker) → all throw
      TxIntentMismatchRecipientError
    • wrong selector, wrong owner-alone, wrong deposit amount, wrong withdraw
      shares → throw
    • missing defiParams and short/empty calldata → fail closed

Ticket: DEFI-614 · Parent: CECHO-1905

@linear-code

linear-code Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

DEFI-614

@ralph-bitgo
ralph-bitgo Bot force-pushed the defi-614-calldata-validation branch 3 times, most recently from 6a7d06c to b9bfb89 Compare August 27, 2026 15:11
@venkateshv1266
venkateshv1266 force-pushed the defi-614-calldata-validation branch from b9bfb89 to 7b1aeb2 Compare August 28, 2026 06:35
Decode DeFi calldata in TSS verification and compare embedded values
against wallet intent; add regression coverage for redirected
recipients and excess approvals.

Ticket: DEFI-614
Session-Id: d90e4bb7-8f74-4cb7-aedb-aa9947b9c0ae
Task-Id: 98400f52-e042-4e3a-88f1-71b74a0737bd
@venkateshv1266
venkateshv1266 force-pushed the defi-614-calldata-validation branch from 7b1aeb2 to 734c375 Compare August 28, 2026 06:48
@venkateshv1266
venkateshv1266 marked this pull request as ready for review August 28, 2026 08:13
@venkateshv1266
venkateshv1266 requested a review from a team as a code owner August 28, 2026 08:13
Comment thread modules/abstract-eth/src/abstractEthLikeNewCoins.ts

@sachinBitgo sachinBitgo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@venkateshv1266
venkateshv1266 merged commit ccb9503 into master Aug 28, 2026
27 checks passed
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.

2 participants