Skip to content

Security: stability_vault seeker unilateral exit can sweep the provider's collateral #68

Description

@tiero

Summary

examples/stability/stability_vault.ark has the same recovery-asymmetry class as #67, in its most severe form: the seeker (counterparty) holds an unconstrained unilateral CSV exit that can sweep the entire totalCollateral — including the provider's collateral — while the provider has no operator-independent recovery path at all.

Severity: High (the seeker can take more than their entitlement, at the provider's expense, and the provider cannot self-recover).

Details

The vault's totalCollateral is funded partly by the taker's userBTC and partly by the provider's offer collateral (stability_offer.ark: totalCollateral = userBTC * (100 + collateralRatioPct) / 100). So the provider is the disadvantaged depositor.

  • The seeker's only raw leaf is function unilateral(signature seekerSig) tapscript { require(older(exit)); require(checkSig(seekerSig, ...)); } — unconstrained, so after older(exit) the seeker can send the whole vault anywhere.
  • The seeker's true entitlement is seekerPayout = clamp(newTargetUSD·1e8 / P, 0, totalCollateral), normally less than totalCollateral. The unilateral leaf lets them take all of it.
  • The provider's recovery paths — providerExit(...), removeCapital(...), settleAndUpdateFunding(...) — are all covenants (server + tweaked emulator, plus an oracle signature). There is no operator-free provider exit. Under operator outage the provider cannot recover collateral, but the seeker's CSV exit still resolves.

Root cause

Same as #67: covenant paths are mandatorily co-signed (src/compiler/tapscript.rs:521-557), and a tapscript leaf cannot pin outputs (src/parser/grammar.pest:41-42, src/parser/tapscript.rs:146-148). The compiler is correct; the contract hands the unconstrained unilateral exit to the wrong party and gives the depositor (provider) no operator-free path.

Fix direction

Two things are needed (they're independent and ideally both):

  1. Give the provider an operator-independent exit — a CSV + checkSig(providerSig, providerPk) tapscript leaf, so the provider can always recover their collateral without the operator.
  2. Constrain the seeker's exit so it cannot exceed seekerPayout. Since a tapscript leaf cannot introspect outputs, this can't be a raw unilateral leaf — the bounded seeker payout must remain a covenant path, and the seeker's unconstrained unilateral leaf should be removed (or its CSV delay set strictly after the provider's exit so the provider always recovers first).

Because a bounded, output-pinning exit is only expressible as a covenant (operator-dependent), the honest resolution here is: the provider — whose principal is at stake — gets the operator-free unilateral exit, and the seeker's over-claiming raw exit is removed.

Companion: the general payment-family instance is #67; the authoring-guidance gap + optional validator lint is tracked separately (linked once filed).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions