Skip to content

fix(parameters): secure multisig against stale approvals, self-degrading quorum, and admin bypass (#107) - #108

Merged
EmeditWeb merged 1 commit into
StepFi-app:mainfrom
KingFRANKHOOD:fix/secure-parameters-multisig-107
Aug 30, 2026
Merged

fix(parameters): secure multisig against stale approvals, self-degrading quorum, and admin bypass (#107)#108
EmeditWeb merged 1 commit into
StepFi-app:mainfrom
KingFRANKHOOD:fix/secure-parameters-multisig-107

Conversation

@KingFRANKHOOD

Copy link
Copy Markdown
Contributor

Summary

Closes #107

Replaces the decorative parameters-contract multisig with real security:

  1. Stale approvals can never be counted. Every Proposal now records a snapshot of the eligible signer set at proposal time. approve() rejects signers who were not members at proposal time, and execute() re-validates every stored approver against both the snapshot and the current signer set before counting it. A signer removed since (or added after) approval instantly loses all approval/veto power over in-flight proposals.
  2. Signer-set changes require elevated quorum. UpdateSigners actions need threshold + 1 approvals, capped at the full signer count (unanimity for already-unanimous sets). A 2-of-3 committee can no longer install a 2-of-2 gate (or admit a colluder) with only 2 old-threshold approvals.
  3. Two-step configure_multisigconfirm_multisig. The admin step now only records a pending config and emits a prominent MSCONFPR event carrying the full proposed signer set; the set is activated only by the explicit confirm_multisig step. A single compromised admin key can no longer silently swap the signer set — the intent is broadcast on-chain first.
  4. In-flight signer-set proposals are invalidated when the signer set changes. A new active-proposal index lets do_update_signers sweep and void every other non-executed UpdateSigners proposal (ProposalInvalidated = 18, PROPIVLD event). Non-signer proposals survive, but any stale approver blocks execution via the membership re-validation.

New errors: ProposalInvalidated = 18, ApproverNotEligible = 19. Existing event surface preserved; only additive events (MSCONFPR, PROPIVLD) were added.

This repo is for Soroban smart contracts only

  • My changes are inside the contracts/ directory
  • I have NOT added any TypeScript, React, or frontend files
  • I have NOT added package.json, vite.config.ts, index.html, or any Node.js files
  • My changes are written in Rust

Type of change

  • Bug fix
  • New contract function
  • Test coverage
  • Storage or type changes
  • Upgrade/migration utility

What changed per file

  • contracts/parameters-contract/src/types.rsProposal gains snapshot: Vec<Address> and invalidated: bool.
  • contracts/parameters-contract/src/errors.rsProposalInvalidated = 18, ApproverNotEligible = 19.
  • contracts/parameters-contract/src/storage.rs — instance keys PMSCFG (pending multisig) and PROPIDS (active-proposal index) plus accessors.
  • contracts/parameters-contract/src/events.rs — additive MSCONFPR (prominent multisig-configure-proposed) and PROPIVLD (proposal invalidated) events.
  • contracts/parameters-contract/src/lib.rs — two-step configure_multisig/confirm_multisig; snapshot capture in propose; snapshot+membership validation in approve/execute; elevated quorum for UpdateSigners; invalidation sweep on signer-set change.
  • contracts/parameters-contract/src/tests.rs — 34 tests, including 15 new security tests.
  • contracts/creditline-contract/src/tests.rs — governance integration test now calls confirm_multisig() between configure and propose.
  • context/progress-tracker.md — updated.

Security tests (new)

Acceptance criteria

  • Removed signers' approvals are never counted — enforced at approve() and execute() against snapshot + current membership.
  • Signer-set changes require elevated quorum — threshold + 1, capped at unanimity.
  • configure_multisig emits a prominent MSCONFPR event and requires a second confirm_multisig step.
  • In-flight signer-set proposals are invalidated when the signer set changes.
  • Exploit reproduction test exists: fails pre-fix / passes post-fix (documented above).
  • Build zero errors; all tests green.

Testing

  • cargo build passes with zero errors
  • cargo test passes — cargo test --locked399 passed, 0 failed (parameters 34, creditline 143, liquidity-pool 109, reputation 60, vendor-registry 26, vouching 27)
  • New tests written for every new function
  • require_auth() is first line of every mutating function I added or changed
  • extend_ttl() called after every persistent storage write I added or changed
  • No .unwrap() or .expect() in user-facing paths

Context files reviewed

  • context/architecture-context.md
  • context/code-standards.md
  • context/progress-tracker.md updated

…ing quorum, and admin bypass (StepFi-app#107)

- Snapshot the eligible signer set into every Proposal; approve() and
  execute() re-validate each approver against the snapshot AND current
  membership so removed signers' approvals are never counted and newly
  added signers cannot influence older proposals (ApproverNotEligible=19).
- Require threshold + 1 approvals (capped at full signer count) for
  UpdateSigners actions so a committee cannot cheapen its own gate.
- Split configure_multisig into a two-step propose->confirm flow with a
  prominent MSCONFPR event before any signer set is activated.
- Invalidate in-flight UpdateSigners proposals when the signer set changes
  (ProposalInvalidated=18, PROPIVLD event).
- Tests: 399 workspace tests green, including the stale-approval exploit
  reproduced end-to-end (fails pre-fix, passes post-fix).

@EmeditWeb EmeditWeb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

✅ Automated Audit: solves

@KingFRANKHOOD Excellent work, thank you! 🎉

The PR genuinely addresses all three root causes from issue #107: (1) admin bypass is fixed via two-step configure/confirm flow where configure_multisig only proposes and emits MSCONFPR event, confirm_multisig applies the change; (2) stale approvals are prevented by snapshot validation at approve() and execute() time — every stored approver must be in the proposal-time snapshot AND current signer set; (3) self-degrading quorum is fixed by requiring threshold+1 for UpdateSigners actions, capped at unanimity. All 34 new tests pass, including the end-to-end exploit reproduction test that specifically verifies the stale-approval scenario from the issue fails pre-fix and passes post-fix. CI shows 399 passed, 0 failed. PR title and description are substantive and of high quality. CI integrity is maintained — no workflow modifications.


CI checks: ✅ PASSED: Build and Test Contracts
Merge conflicts: ✅ none — but the PR is blocked (failing/missing required checks or reviews).

Audited by stepfi-audit-bot 🤖

@EmeditWeb
EmeditWeb merged commit 240f931 into StepFi-app:main Aug 30, 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.

critical: parameters multisig is decorative — admin bypass, stale approvals, self-updatable signers

2 participants