ut# Stellar-Save Security Guide
This guide covers security best practices for everyone who interacts with Stellar-Save — from end users managing their savings groups to developers building on or contributing to the protocol.
Reporting a vulnerability? Do not open a public issue. Use GitHub Private Security Advisories. See SECURITY.md for the full disclosure policy.
- Wallet Security
- Smart Contract Security
- Common Scams and How to Avoid Them
- Incident Response
- Security Checklist
- Review and Audit Status
Your Stellar wallet is the only key to your funds. If your private key or seed phrase is compromised, no smart contract protection can recover your money.
- Write it down on paper and store it somewhere physically secure (e.g. a safe). Do not photograph it or store it in cloud notes, email, or messaging apps.
- Never type your seed phrase into any website, browser extension prompt, or app that you did not deliberately open yourself.
- No legitimate Stellar-Save interface, support channel, or maintainer will ever ask for your seed phrase.
For any meaningful amount of XLM, use a hardware wallet (Ledger or Trezor) with Freighter or Ledger Live. Hardware wallets sign transactions in an isolated environment — even if your computer is compromised, your private key stays on the device.
- Testnet experimentation: a software wallet (Freighter browser extension) is fine
- Mainnet with real funds: hardware wallet strongly recommended
Every transaction you sign moves real funds. Before approving:
- Check the contract ID matches the official Stellar-Save deployment (published in the repository's
environments.tomland release notes) - Check the function name — legitimate operations are
contribute,join_group,execute_payout,pause_group,unpause_group - Check the amount — Stellar-Save never requires you to send more than your group's
contribution_amount - Use Stellar Laboratory to inspect any transaction XDR before signing if you are unsure
- Update Freighter, Ledger firmware, and your browser regularly
- Only install wallet extensions from official sources (Chrome Web Store / Firefox Add-ons, verified publisher)
- Be cautious of wallet extensions that request broad permissions
Use a dedicated wallet address for Stellar-Save groups rather than your primary holding address. This limits exposure if a group interaction goes wrong.
The Stellar-Save Soroban contract includes the following protections:
| Protection | Implementation |
|---|---|
| Reentrancy guard | Applied to transfer_payout() — prevents recursive calls during fund transfer |
| Overflow checks | overflow-checks = true in the release profile (Cargo.toml) |
| Panic = abort | panic = "abort" prevents stack-unwinding exploits |
| Atomic storage | All state changes are committed atomically — no partial updates |
| Authorization checks | Every privileged operation verifies the caller via env.invoker() |
| Creator-only pause | pause_group / unpause_group restricted to the group creator |
| Rate limiting | Group creation and joins are rate-limited to prevent spam |
| Emergency withdrawal | Members can reclaim pro-rata funds after 2× cycle duration of inactivity |
- Group creator: can pause/unpause the group and configure parameters at creation time
- Members: can contribute and, when it is their turn, receive the payout
- Anyone: can call
execute_payout— the contract validates eligibility internally; there is no privileged executor
There is no global admin key that can move user funds. The contract is non-upgradeable once deployed — what you audit is what runs.
On-chain storage keys are namespaced per group and per member using StorageKeyBuilder. There is no shared mutable state between groups — a bug or attack in one group cannot affect another.
- No oracle: cycle deadlines use Stellar ledger timestamps. These are reliable but cannot be manipulated by users.
- No slippage protection: contributions are in exact XLM amounts; there is no price-sensitive swap logic.
- Group size: very large groups (>50 members) may approach Soroban instruction limits. The recommended maximum is 20 members.
- No upgrade path: the contract is immutable. If a critical bug is found, the mitigation is to pause affected groups and deploy a new contract version. Existing groups would need to migrate manually.
- Always call
get_group()and validate the returned state before building a transaction - Use
get_contribution_status(group_id, cycle)to check what is owed before callingcontribute() - Subscribe to Soroban events (
PayoutExecuted,ContributionReceived,ContractPaused) for real-time monitoring rather than polling storage - Test all integrations on testnet before mainnet. Use a separate funded testnet identity
Blockchain savings platforms attract social engineering attacks. These are the most common patterns targeting ROSCA users.
How it works: A scammer creates a group with a very attractive contribution amount or promises guaranteed returns. They collect contributions from early members and disappear before the payout rotation reaches them.
How to avoid it:
- Only join groups where you personally know and trust the creator and other members
- Call
get_group(group_id)and inspect the creator address before joining - Check the creator's on-chain history using Stellar Expert — look for prior group activity
- Legitimate ROSCAs do not promise returns beyond the pooled contributions
How it works: Someone contacts you via Telegram, Discord, or email claiming to be a Stellar-Save maintainer. They ask you to send funds to a "recovery address", share your seed phrase, or approve a transaction to "verify your wallet".
How to avoid it:
- The Stellar-Save team will never DM you first asking for funds or credentials
- All official communication happens through GitHub Issues, GitHub Discussions, and the official Telegram channel listed in the README
- If someone claims to be a maintainer, verify by checking their GitHub profile and contribution history
How it works: A fake website mimics the Stellar-Save frontend. It prompts you to connect your wallet and then requests approval for a malicious transaction.
How to avoid it:
- Bookmark the official frontend URL and always navigate from your bookmark
- Check the browser address bar carefully — phishing sites often use lookalike domains (e.g.
stellar-save.iovsstellarsave.app) - Freighter will show the contract ID being called — verify it matches the official deployment before approving
- If a site asks for your seed phrase, close it immediately
How it works: A scammer in a group chat claims to be the group admin and asks members to send funds directly to a wallet address "to fix a problem" or "to speed up the payout".
How to avoid it:
- All contributions go through the smart contract, never to a personal wallet address
- The contract enforces the exact contribution amount — no one can legitimately ask you to send more
- If someone in your group is asking for direct transfers, treat it as a scam and alert other members
How it works: A scammer deploys a contract that looks like Stellar-Save but has a backdoor allowing them to drain funds.
How to avoid it:
- Always verify the contract ID against the official deployment listed in the repository
- The official contract IDs for each network are published in
environments.tomland in release notes - You can inspect any Soroban contract's WASM on Stellar Expert and compare it against the published build
This section covers what to do when something goes wrong — for users experiencing a problem and for developers responding to a security event.
- Check the transaction on Stellar Expert using your wallet address — confirm it was submitted and succeeded
- Call
get_contribution_status(group_id, cycle_number)to check the on-chain record - If the transaction succeeded but the contract state is wrong, open a GitHub issue with the transaction hash
- Missing a contribution delays the payout for the entire group for that cycle
- You can still contribute in the next cycle — call
contribute(group_id, member, amount)when the next cycle opens - Your position in the payout rotation is not affected by a missed cycle
- Call
get_missed_contributions(group_id, current_cycle)to identify who has not contributed - Contact those members directly
- If the group has been inactive for 2× the cycle duration, any member who has not yet received a payout can call
emergency_withdraw()to reclaim their pro-rata contributions
- Call
get_group(group_id)and check thepausedfield - A paused group cannot accept contributions or execute payouts, but funds are safe
- Only the group creator can pause or unpause — contact them directly
- Do not send any more funds
- Document everything: transaction hashes, wallet addresses, screenshots of communications
- Open a GitHub issue (for contract-level issues) or report to the Stellar community
- If funds have been stolen, report to relevant local authorities — blockchain transactions are traceable
The full operational incident response process is documented in docs/incident-response-plan.md. Summary:
| Severity | Definition | Response time |
|---|---|---|
| P1 — Critical | Funds at risk or contract failure | 15 minutes |
| P2 — High | Degraded service, data loss risk | 1 hour |
| P3 — Medium | Partial degradation | 4 hours |
| P4 — Low | Minor issue, no user impact | Next business day |
Immediate steps for a P1 security event:
- Do not discuss details publicly until contained
- Pause affected groups:
bash scripts/dr_recover.sh pause-all-groups
- Open a GitHub Private Security Advisory
- Assess whether a contract rollback or new deployment is needed
- Communicate to users via the status page — do not leave them without information
For key compromise, contract rollback, and data recovery procedures see the runbooks in docs/runbooks/.
- I verified the contract ID against the official deployment in
environments.toml - I inspected the group parameters with
get_group(group_id)before joining - I know and trust the group creator and other members
- I am using a hardware wallet (or understand the risk of using a software wallet)
- My seed phrase is stored securely offline and I have not shared it with anyone
- I have tested the flow on testnet before using mainnet
- I contribute the exact required amount — no more, no less
- I monitor my group's cycle status with
get_contribution_status() - I have noted the contribution deadline for each cycle
- I have not shared my private key or seed phrase with anyone claiming to be support
-
cargo auditpasses with no critical or high advisories -
npm auditpasses with no critical or high advisories - All contract tests pass:
cargo test -p stellar-save - Semgrep and CodeQL scans are clean (check the Security tab)
- Contract ID is published in
environments.tomland release notes - Emergency pause has been tested on testnet
- Monitoring alerts are configured for
ContractPausedandPayoutExecutedevents
- The change has been reviewed by at least two maintainers
- New authorization paths are covered by tests
- No new
unwrap()orexpect()calls on untrusted input - No new
panic!calls in contract code — useContractErrorvariants - Storage key changes are backward-compatible or include a migration
- The PR does not introduce new external dependencies without review
| Tool | Scope |
|---|---|
| Semgrep | SAST — Rust and TypeScript |
| CodeQL | SAST — JavaScript/TypeScript |
| Snyk | Dependency CVEs (npm + Cargo) |
| cargo-audit | Rust advisory database |
| npm audit | Node advisory database |
| Gitleaks | Secret detection |
| Dependabot | Automated dependency updates |
PRs targeting main are blocked from merging if any critical or high severity finding is detected.
The contract has been reviewed internally. A third-party audit is recommended before any significant mainnet deployment. If you are a security researcher and have found an issue, please follow the responsible disclosure process in SECURITY.md.
Property-based and fuzz tests live in contracts/stellar-save/src/fuzz_tests.rs. Run them with:
cargo test -p stellar-save fuzzThe fuzzing strategy is documented in docs/fuzzing-strategy.md.
For questions about this guide, open a GitHub Discussion. For vulnerabilities, use GitHub Private Security Advisories.