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.
- Primary: Tokens held in the vault contract
- Secondary: Governance control (ability to modify signers/policies)
- Threshold Honesty: At least
thresholdsigners are honest and have secure key management - Network Security: Stellar network and Soroban runtime are secure
- Token Contract: The wrapped token contract is non-malicious
- Frontend: Users verify contract addresses (frontend can be compromised)
We consider attackers who can:
- ✅ Control up to
threshold - 1signer 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
Every mutable function enforces cryptographic authorization:
signer.require_auth(); // Ensures transaction is signed by signer's private keyGuarantees:
- 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)
| 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) |
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
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.
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:
- Detect malicious proposal on-chain
- Deploy new vault and migrate funds
- Coordinate off-chain response
Recommended Timelock: 24-72 hours for production treasuries.
Scenario: Attacker gains access to one signer's private key.
Impact: ❌ None (threshold not met)
Mitigation:
- Set
threshold >= 2 - Monitor proposals for suspicious activity
Scenario: Attacker controls threshold - 1 keys.
Impact:
Mitigation:
- Off-chain monitoring alerts remaining signers
- Initiate signer rotation via honest majority
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)
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 harmlessGuarantee: Proposal cannot be executed twice (checked at function start).
Scenario: Attacker creates thousands of junk proposals to:
- Fill storage
- Make monitoring difficult
- Increase gas costs
Impact:
Mitigation:
- Gas costs make spam expensive
- Off-chain indexers can filter by proposer reputation
- Future: Add proposal deposit requirement
Scenario: Attacker sees legitimate proposal in mempool and front-runs with conflicting transaction.
Impact:
Mitigation:
- Stellar's transaction ordering is deterministic
- Use higher fees for time-sensitive operations
- Monitor mempool (if exposed by validators)
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)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".
Current Design: Immutable contract. No upgrade mechanism.
Rationale:
- Prevents rug pulls
- Simplifies auditing
- Forces explicit migration for changes
Migration Process:
- Deploy new contract version
- Create proposal in old vault: "Transfer all funds to new vault"
- Approve and execute
- Verify new vault has funds
- Update frontend to point to new address
Future: Investigate proxy patterns if upgrade flexibility becomes critical.
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
If you discover a security vulnerability, please:
- DO NOT open a public GitHub issue
- DO NOT exploit the vulnerability
- DO email security@stellarvault.io (replace with actual contact)
- 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
-
Key Management:
- Use hardware wallets (Ledger, Trezor) for signer keys
- Never store private keys in plaintext
- Use separate keys for each vault
-
Proposal Review:
- Verify recipient addresses off-chain
- Check amount against expected invoices
- Confirm with other signers via secure channel
-
Operational Security:
- Use dedicated device for signing
- Enable 2FA on all related accounts
- Regularly rotate signer keys (every 6-12 months)
-
Configuration:
- Set
threshold >= 2(minimum) - Set
timelock >= 86400(24 hours) for large transfers - Set
daily_limitto expected monthly spending / 30
- Set
-
Monitoring:
- Subscribe to on-chain events (proposals, executions)
- Set up alerts for unusual activity
- Maintain audit log of all decisions
-
Disaster Recovery:
- Document signer contact info (encrypted)
- Maintain cold backup of key shares
- Establish emergency multisig for migrations
- Soroban Security Best Practices
- Smart Contract Security Verification Standard
- DeFi Security Best Practices
Last Updated: 2026-07-01
Version: 0.1.0
Reviewers: [Pending Audit]