Skip to content

Keep proxy auth tokens at the wallet address - #410

Merged
Andre-Diamond merged 5 commits into
mainfrom
pr/Olisehgenesis/396
Oct 11, 2026
Merged

Andre-Diamond merged 5 commits into
mainfrom
pr/Olisehgenesis/396

Conversation

@Andre-Diamond

Copy link
Copy Markdown
Collaborator

Summary

The deployed proxy spend validator only checks that some output carries an auth token. It does not check the signer or the destination. A proxy wallet holds up to 10 of those tokens. If one lands anywhere other than the multisig wallet address — including as change at the proxy address — anyone can satisfy the validator and drain the proxy. This is an off-chain defense for that immutable validator.

  • Builders return every spent auth token to the wallet address. Spend, DRep, and vote transactions split them one per output so the pool of independently spendable tokens stays intact. The cleanup sweep bundles them into the swept-value output so that UTxO still has real ADA for the following burn. proxySpend also returns any auth token found among the proxy-address inputs, so a token that already leaked there can be recovered.
  • The finished CBOR is audited before it is stored or signed. src/lib/proxy/authTokenAudit.ts decodes the transaction and requires every auth-token output to be at the wallet script address. v1 proxy handlers run that check before persisting (a failure is a 500, because the builder itself misbehaved). signTransaction and tRPC createTransaction, importTransaction, and updateTransaction refuse a pending transaction that would move a token (409 / PRECONDITION_FAILED). The check covers every Proxy row for the wallet, active or not, because deactivating or deleting a proxy is a soft delete and does not remove tokens from the chain.
  • Burns are gated on an empty proxy address. Burning the auth-token policy is irreversible. If the proxy address still holds UTxOs, those funds would be locked forever, so the audit refuses the burn until they are swept.
  • The browser asks the server before wallet.signTx. The last signer broadcasts before the signature is recorded, so a later server refusal would be too late. transaction.preSignProxyAudit runs the same audit first and fails closed if it cannot be reached.
  • freeUtxos marks auth-token UTxOs. Each UTxO now includes authToken and authTokenId for every proxy row of the wallet, so bots can keep permission tokens out of generic coin selection. Spend, DRep, and vote calls should pass exactly one token UTxO; cleanup needs all of them.
  • New proxies use the proxy script's own hash as a script stake credential. Withdrawals and delegation of proxy rewards then go through the same validator as spends. Existing proxies keep their stored address: handlers, the browser contract, and CI recovery derive the stake credential from that address instead of re-deriving with today's default.

Olisehgenesis and others added 4 commits September 13, 2026 11:34
…ted change

buildProxySpendTx (and buildProxyCleanupSweepTx) only excluded the single
designated authTokenUtxo from caller-supplied walletUtxos before spending
them and sending changeAddress(proxyAddress). A wallet holds up to 10
AuthToken UTxOs (minted at proxy setup, all sent to the wallet's own
address). If any *other* AuthToken UTxO was included in walletUtxos - e.g.
by a bot/integration doing ordinary coin selection via freeUtxos, which
returned all UTxOs undifferentiated - it would silently become change at
proxyAddress. The on-chain proxy spend validator only checks that an
AuthToken appears in *some* output, with no signer/quorum check, so once
an AuthToken sits at proxyAddress unprotected, anyone can drain the address
with a raw transaction - no compromised keys required.

Changes:
- utxoUtils: add assertNoStrayAuthTokenUtxos(), a fail-closed guard that
  throws if walletUtxos contains any AuthToken-bearing UTxO other than the
  one explicitly designated.
- txBuilders: call the guard in buildProxySpendTx and
  buildProxyCleanupSweepTx before building the transaction.
- api/v1/freeUtxos: flag each returned UTxO with authToken: true/false
  (based on the wallet's active proxy AuthToken policy id) so callers have
  a positive signal to avoid selecting more than the one they intend.
- SKILL.md: document the authToken flag and the exactly-one-AuthToken-UTxO
  requirement for proxySpend callers.
- tests: new proxyAuthTokenLeakGuard.test.ts covering the guard and
  buildProxySpendTx rejection; updated freeUtxos.bot.test.ts mocks/assertions
  for the new authToken field.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…nsactions

- Updated SKILL.md to clarify proxy auth token handling and safety measures.
- Enhanced README.md with details on auth-token hygiene and transaction requirements.
- Improved CI documentation for proxy lifecycle and cleanup processes.
- Added tests for auth token conservation and auditing to ensure compliance with wallet address constraints.
- Introduced new utility functions for auditing transactions involving proxy auth tokens.
- Removed outdated proxy auth token leak guard tests, consolidating functionality into new audit mechanisms.
…on auditing

- Updated SKILL.md to clarify proxy auth token handling and safety measures.
- Enhanced README.md with details on auth-token hygiene and transaction requirements.
- Improved CI documentation for proxy lifecycle and cleanup processes.
- Added tests for auth token conservation and auditing to ensure compliance with wallet address constraints.
- Introduced new utility functions for auditing transactions involving proxy auth tokens.
- Consolidated proxy address stake credential handling to ensure consistency across implementations.
@vercel

vercel Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
multisig Ready Ready Preview Oct 11, 2026 8:16am UTC

Request Review

…ion authorization tests

- Enhanced transaction authorization tests to verify that the proxy's findMany method is called with the correct walletId.
- Updated mock database to include a proxy implementation that returns an empty array for findMany calls.
@Andre-Diamond
Andre-Diamond merged commit b56d907 into main Oct 11, 2026
11 checks passed
Andre-Diamond added a commit that referenced this pull request Oct 11, 2026
Brings in the proxy auth-token hardening (PR #410) and VoteDelegation
certificates on top of the Plutus transaction controls.

Conflict resolutions:
- tx-draft types: DraftCertificateKind gains "VoteDelegation".
- validate.ts / to-tx-builder.ts: keep both import sets; drop preprod's
  fee-floor constants, which this branch already moved into funding.ts.
- useTransaction.ts: keep the pre-sign proxy audit before signTx and add
  ctx to the callback deps.
- useTransactionReview.test.ts: mock preSignProxyAudit so the hook reaches
  signTx, and cover the refusal path.

Co-authored-by: Cursor <cursoragent@cursor.com>

This branch was successfully deployed

1 active deployment
Preview — 8c4d2af9 Deployed Oct 11, 2026 by vercel[bot]
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