Skip to content

Security: Praisefotos1/stellarvault

Security

docs/SECURITY.md

Security Documentation

Security Model

StellarVault is designed to secure treasury funds through multi-signature authorization combined with programmable spending policies. This document outlines the security guarantees, known risks, and responsible disclosure process.

Threat Model

Assets at Risk

  • Primary: Tokens held in the vault contract
  • Secondary: Governance control (ability to modify signers/policies)

Trust Assumptions

  1. Threshold Honesty: At least threshold signers are honest and have secure key management
  2. Network Security: Stellar network and Soroban runtime are secure
  3. Token Contract: The wrapped token contract is non-malicious
  4. Frontend: Users verify contract addresses (frontend can be compromised)

Adversary Capabilities

We consider attackers who can:

  • ✅ Control up to threshold - 1 signer keys
  • ✅ Submit arbitrary transactions to the network
  • ✅ Analyze all on-chain data
  • ✅ Attempt reentrancy attacks
  • ✅ Front-run transactions
  • ❌ Break cryptographic primitives (Ed25519, SHA-256)
  • ❌ Compromise Stellar validator consensus

Security Guarantees

What require_auth() Protects

Every mutable function enforces cryptographic authorization:

signer.require_auth();  // Ensures transaction is signed by signer's private key

Guarantees:

  • No one can propose/approve on behalf of another signer
  • No one can execute without proper authorization
  • Replay attacks are prevented by Soroban's nonce system

Does NOT protect against:

  • Compromised private keys (if attacker has key, they ARE authorized)
  • Social engineering (convincing signers to approve malicious proposals)

Authorization Matrix

Function Who Can Call Checks Performed
initialize Anyone (once) None (one-time setup)
propose_transfer Current signers require_auth() + is_signer()
propose_signer_change Current signers require_auth() + is_signer() + validation
approve_transfer Current signers require_auth() + is_signer() + not already approved
revoke_approval Current signers require_auth() + is_signer() + was approved
execute_transfer Current signers require_auth() + is_signer() + threshold + timelock + daily limit
get_proposal Anyone None (read-only)
get_signers Anyone None (read-only)
get_config Anyone None (read-only)

Spending Policy Enforcement

Threshold (M-of-N)

Guarantee: No transfer can execute with fewer than threshold approvals.

Implementation:

if proposal.approvals.len() < config.threshold {
    return Err(VaultError::ThresholdNotMet);
}

Edge Cases Covered:

  • Proposer counts as first approval
  • Revoking approval reduces count
  • Threshold must be 1 <= T <= N

Daily Limit

Guarantee: Total spending in any 24-hour period cannot exceed daily_limit.

Implementation:

let new_daily_total = daily_spending.amount + amount;
if new_daily_total > config.daily_limit {
    return Err(VaultError::DailyLimitExceeded);
}

Edge Cases Covered:

  • Limit resets at day boundaries (UTC)
  • Concurrent proposals are aggregated correctly
  • Single transfer above limit is rejected

Known Limitation: An attacker with threshold keys could drain funds over multiple days, respecting daily limit.

Timelock

Guarantee: Transfers >= large_transfer_threshold cannot execute until timelock_seconds have passed.

Implementation:

if env.ledger().timestamp() < expires_at {
    return Err(VaultError::TimelockActive);
}

Attack Mitigation: Even if an attacker compromises threshold keys, honest signers have time to:

  1. Detect malicious proposal on-chain
  2. Deploy new vault and migrate funds
  3. Coordinate off-chain response

Recommended Timelock: 24-72 hours for production treasuries.

Attack Scenarios

1. Compromised Single Key

Scenario: Attacker gains access to one signer's private key.

Impact: ❌ None (threshold not met)

Mitigation:

  • Set threshold >= 2
  • Monitor proposals for suspicious activity

2. Compromised Majority (< threshold)

Scenario: Attacker controls threshold - 1 keys.

Impact: ⚠️ Can spam proposals but cannot execute

Mitigation:

  • Off-chain monitoring alerts remaining signers
  • Initiate signer rotation via honest majority

3. Compromised Threshold

Scenario: Attacker controls threshold or more keys.

Impact: 🔴 Can execute transfers up to daily limit immediately

Mitigation:

  • Timelock delays large transfers
  • Honest signers can front-run with emergency migration
  • Set aggressive rate limits for high-risk vaults

Post-Compromise:

  • Deploy new vault
  • Transfer remaining funds
  • Revoke old vault approval with token issuer (if supported)

4. Reentrancy Attack

Scenario: Malicious token contract calls back into vault during transfer().

Vulnerability: Could vault be tricked into double-spending?

Mitigation: All state updates happen BEFORE external call:

proposal.executed = true;
set_proposal(&env, &proposal);  // ✅ State updated first

// External call
token_client.transfer(...);  // ✅ Reentrancy harmless

Guarantee: Proposal cannot be executed twice (checked at function start).

5. Griefing (Spam Proposals)

Scenario: Attacker creates thousands of junk proposals to:

  • Fill storage
  • Make monitoring difficult
  • Increase gas costs

Impact: ⚠️ Annoyance, but no fund loss

Mitigation:

  • Gas costs make spam expensive
  • Off-chain indexers can filter by proposer reputation
  • Future: Add proposal deposit requirement

6. Front-Running

Scenario: Attacker sees legitimate proposal in mempool and front-runs with conflicting transaction.

Impact: ⚠️ Can steal priority, but cannot bypass authorization

Mitigation:

  • Stellar's transaction ordering is deterministic
  • Use higher fees for time-sensitive operations
  • Monitor mempool (if exposed by validators)

7. Time Manipulation

Scenario: Can attacker manipulate env.ledger().timestamp() to bypass timelock?

Vulnerability: Validator-controlled timestamp

Mitigation: Stellar validators maintain clock synchronization. Tampering requires compromising consensus (out of scope).

Recommendation: For critical timelocks, use block numbers instead:

timelock_expires_at: Some(current_ledger + blocks_per_day)

Error Handling

All errors are explicit and return informative codes:

#[contracterror]
pub enum VaultError {
    Unauthorized = 1,         // Not a signer
    AlreadyInitialized = 2,   // Contract already set up
    NotFound = 3,             // Proposal doesn't exist
    AlreadyApproved = 4,      // Already approved this proposal
    ThresholdNotMet = 5,      // Not enough approvals
    TimelockActive = 6,       // Too early to execute
    InvalidThreshold = 7,     // Threshold validation failed
    DailyLimitExceeded = 8,   // Would exceed daily limit
    AlreadyExecuted = 9,      // Proposal already executed
    NotApproved = 10,         // Signer didn't approve
    SignerNotFound = 11,      // Signer doesn't exist
    SignerAlreadyExists = 12, // Signer already exists
}

Security Note: Errors leak minimal information. An attacker cannot distinguish "valid proposal ID, wrong threshold" from "invalid proposal ID".

Upgrade Policy

Current Design: Immutable contract. No upgrade mechanism.

Rationale:

  • Prevents rug pulls
  • Simplifies auditing
  • Forces explicit migration for changes

Migration Process:

  1. Deploy new contract version
  2. Create proposal in old vault: "Transfer all funds to new vault"
  3. Approve and execute
  4. Verify new vault has funds
  5. Update frontend to point to new address

Future: Investigate proxy patterns if upgrade flexibility becomes critical.

Audit Status

⚠️ This contract has NOT been audited.

Use at your own risk in production.

Before Mainnet:

  • Formal security audit by reputable firm (Trail of Bits, OpenZeppelin, etc.)
  • Fuzzing with property-based testing
  • Testnet stress testing (1000+ proposals, 50+ signers)
  • Economic analysis of gas costs under attack
  • Review by Stellar Foundation security team

Responsible Disclosure

If you discover a security vulnerability, please:

  1. DO NOT open a public GitHub issue
  2. DO NOT exploit the vulnerability
  3. DO email security@stellarvault.io (replace with actual contact)
  4. Include:
    • Vulnerability description
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)

Response Timeline:

  • Acknowledgment within 24 hours
  • Initial assessment within 72 hours
  • Fix deployed within 30 days (depending on severity)

Bug Bounty: We offer rewards for critical vulnerabilities:

  • Critical (fund theft): $5,000 - $50,000
  • High (auth bypass): $1,000 - $5,000
  • Medium (DoS, griefing): $500 - $1,000

Security Best Practices for Users

For Signers

  1. Key Management:

    • Use hardware wallets (Ledger, Trezor) for signer keys
    • Never store private keys in plaintext
    • Use separate keys for each vault
  2. Proposal Review:

    • Verify recipient addresses off-chain
    • Check amount against expected invoices
    • Confirm with other signers via secure channel
  3. Operational Security:

    • Use dedicated device for signing
    • Enable 2FA on all related accounts
    • Regularly rotate signer keys (every 6-12 months)

For Deployers

  1. Configuration:

    • Set threshold >= 2 (minimum)
    • Set timelock >= 86400 (24 hours) for large transfers
    • Set daily_limit to expected monthly spending / 30
  2. Monitoring:

    • Subscribe to on-chain events (proposals, executions)
    • Set up alerts for unusual activity
    • Maintain audit log of all decisions
  3. Disaster Recovery:

    • Document signer contact info (encrypted)
    • Maintain cold backup of key shares
    • Establish emergency multisig for migrations

References


Last Updated: 2026-07-01
Version: 0.1.0
Reviewers: [Pending Audit]

There aren't any published security advisories