Do not open a public GitHub issue for security vulnerabilities.
Please report them privately via GitHub Security Advisories. We will acknowledge your report within 48 hours and aim to release a patch within 14 days for confirmed critical issues.
Include in your report:
- A description of the vulnerability and its potential impact
- Steps to reproduce or a proof-of-concept
- Any suggested mitigations (optional)
The following components are explicitly trusted by the SoroStream contracts:
| Component | Trust level | Rationale |
|---|---|---|
Token contract (token::Client) |
Fully trusted | The token address is supplied by the stream creator. SoroStream makes no attempt to validate the token contract's correctness, fee model, or rebase behaviour. Callers are responsible for using well-audited SEP-41 tokens. |
Ledger timestamp (env.ledger().timestamp()) |
Trusted | All time accounting relies on the Stellar network's ledger-close timestamp. A validator coalition controlling ledger close times could manipulate stream vesting. |
| Sender / recipient / cosigner addresses | Trusted as supplied | SoroStream does not perform identity verification. It only enforces require_auth(). |
| Soroban runtime / host functions | Fully trusted | The contract has no defence against a compromised Soroban host. |
The following limitations are by design or explicitly out of scope for the current version:
Stream creation, claim, renewal, and proposal approval are all single-transaction operations without commit-reveal or ordering protection. A validator or block builder with privileged ordering could:
- Front-run a
claimwith acancelto reduce recipient payout. - Race a
discard_proposalagainstapprove_proposal.
No mitigation is planned in v1. Consider time-locks or commit-reveal schemes for high-value streams.
rate_per_second is denominated in raw token units. There is no integration with price oracles. USD-denominated streaming rates require an off-chain recalculation and a new stream.
deposit must be exactly divisible by duration. Streams where the deposit does not divide cleanly will be rejected. This avoids off-by-one rounding exploits but limits flexibility.
Soroban has no on-chain scheduler. Auto-renewal (renew()) is pull-based and must be triggered by a transaction. A stream will not renew itself if no one calls renew().
Soroban's host executes contract calls atomically and does not support callbacks, so classic EVM-style reentrancy is not applicable. Cross-contract reentrancy via token callbacks is theoretically possible with a malicious token; see the token trust assumption above.
rate_per_second * elapsed_seconds is computed as i128. Streams with extremely high deposits and very long durations could theoretically overflow, but i128 supports values up to ~1.7 × 10³⁸, which is well beyond any realistic token supply.
get_reputation scores are stored in instance storage and increment monotonically on each renew(). There is no decay, dispute mechanism, or slash. Treat the score as a raw activity counter, not a security guarantee.
Proposal expiry is measured in ledger sequence numbers, not wall-clock time. Ledger close time varies (~5–6 s on average). An approval_window_ledgers of 100 corresponds to roughly 8–10 minutes but is not an exact wall-clock guarantee.
The following are explicitly out of scope for the SoroStream bug bounty and security review. Auditors should not spend time on them:
- Malicious token contracts — The token is trusted (see above). A token that steals funds on
transferis the token's bug, not SoroStream's. - Social engineering / private key compromise — Loss of sender or recipient keys is out of scope.
- Stellar network-level consensus attacks — Validator set compromise, ledger rollback, or network halt.
- Gas / resource exhaustion on the Stellar network — Stellar's fee market is outside contract scope.
- UI / front-end vulnerabilities — This repo only covers the on-chain contract.
- Reputation gaming via self-streaming — A sender can stream to themselves to inflate reputation. This is a known design trade-off.
- Griefing by never calling
renew()— If a sender never triggers renewal, the stream simply does not continue. Recipients who rely on renewal should monitor expiry off-chain. discard_proposalrace between sender retraction and cosigner approval — Both transactions are valid operations; the network's natural ordering resolves the race.
| Version | Supported |
|---|---|
main |
✅ Yes |
| Others | ❌ No |
Only the latest commit on main receives security patches.