Skip to content

fix(escrow): guard against self-escrow and arbiter collisions#32

Merged
abayomicornelius merged 9 commits into
StellarSend:mainfrom
chonilius:fix/escrow-form-self-escrow-guard
Jul 16, 2026
Merged

fix(escrow): guard against self-escrow and arbiter collisions#32
abayomicornelius merged 9 commits into
StellarSend:mainfrom
chonilius:fix/escrow-form-self-escrow-guard

Conversation

@chonilius

Copy link
Copy Markdown
Contributor

Summary

Fixes #23. EscrowForm's zod schema validated that beneficiaryPublicKey and arbiterPublicKey were individually well-formed Stellar addresses, but never cross-checked them against each other or the connected wallet's own public key. Since getEscrowPermissions lets the beneficiary or arbiter release funds at any time before the unlock time, this allowed:

  • Self-escrow: depositor sets themselves as beneficiary and can release to themselves immediately, defeating the locking guarantee.

  • No-op arbiter: arbiter set to the depositor's own address, or to the same address as the beneficiary, which collapses the three-party trust model.

  • EscrowForm now accepts a depositorPublicKey prop (the connected wallet's address) and the zod schema adds cross-field refinements:

    • beneficiaryPublicKey !== depositorPublicKey
    • arbiterPublicKey !== depositorPublicKey
    • arbiterPublicKey !== beneficiaryPublicKey
  • EscrowPage passes publicKey from useWallet() into EscrowForm.

  • Submit stays disabled with an inline field error until the conflict is resolved.

Test plan

  • npx vitest run — 59/59 tests pass, including 4 new cases in EscrowForm.test.tsx (self-escrow rejected, arbiter === depositor rejected, arbiter === beneficiary rejected, valid distinct addresses submit successfully)
  • npx tsc --noEmit — no errors
  • npm run build — succeeds

EscrowForm's zod schema validated address format but never compared
beneficiaryPublicKey to the connected wallet's own public key, letting
a depositor name themselves as beneficiary with no locking guarantee.
EscrowForm had no way to compare beneficiaryPublicKey/arbiterPublicKey
against the depositor's own address since it never received it as a
prop. EscrowPage already has publicKey from useWallet, so thread it
through.
Guards against a regression of the beneficiary === depositor hole
described in StellarSend#23.
Adds cases for arbiter === depositor, arbiter === beneficiary, and a
happy-path check that three distinct addresses still allow submit.
@chonilius

Copy link
Copy Markdown
Contributor Author

please review all ci passed

The repo shipped eslint + typescript-eslint + react-hooks/react-refresh
as devDependencies and an `npm run lint` script, but no .eslintrc ever
existed, so `eslint .` failed outright with "couldn't find a
configuration file" — the Lint CI check was failing on every push
regardless of code changes.
Now that lint actually runs, clean up unused testing-library imports
flagged by @typescript-eslint/no-unused-vars in existing test files.
Fixes the remaining no-unused-vars warnings surfaced by the newly
added ESLint config (unused destructured state, icon imports, and
recharts imports).
useWalletContext is intentionally co-located with WalletProvider; this
is the standard pattern for context files and doesn't affect Fast
Refresh in practice.
@chonilius

Copy link
Copy Markdown
Contributor Author

Good Job

@abayomicornelius
abayomicornelius merged commit 90a757b into StellarSend:main Jul 16, 2026
4 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.

EscrowForm has no guard against beneficiary === depositor or arbiter === beneficiary (self-escrow / no-op arbiter)

2 participants