This document describes the security model of FlowPay, known limitations, and how to report vulnerabilities.
FlowPay is deployed on Testnet only and has not been formally audited. It should not be used to manage real funds on Mainnet until an independent security audit has been completed.
FlowPay never holds user funds. It uses the Soroban token interface's transfer_from mechanism — the same pattern as ERC-20 approve + transferFrom on Ethereum.
The flow is:
- User calls
approve()on the token contract, granting FlowPay a spending allowance - FlowPay calls
transfer_from()to move funds from user → merchant - The token contract enforces that the transferred amount does not exceed the approved allowance
This means:
- FlowPay cannot move more than the user has approved
- Users can revoke access at any time by calling
approve()withamount = 0on the token contract - Even if the FlowPay contract were compromised, it could only spend up to the approved allowance
Every function that mutates user state or moves user funds calls user.require_auth():
| Function | Auth check |
|---|---|
subscribe() |
user.require_auth() |
pay_per_use() |
user.require_auth() |
cancel() |
user.require_auth() |
pause() |
user.require_auth() |
resume() |
user.require_auth() |
charge() |
None (intentionally permissionless — see below) |
initialize() |
None (one-time setup) |
charge() has no auth requirement by design. This allows keeper services to trigger charges without holding user private keys. The contract enforces correctness independently:
- The subscription must exist
activemust betrue- The billing interval must have elapsed
If any condition fails, the transaction panics and no funds move. A malicious caller cannot extract funds by calling charge() — they can only trigger a legitimate charge that the user already consented to when subscribing.
The contract checks for the existence of DataKey::Token before writing it. A second call to initialize() panics immediately. This prevents an attacker from re-initializing the contract with a malicious token address after deployment.
The FlowPay contract has no upgrade mechanism. Once deployed, the code is immutable. This is a deliberate security choice — it means no admin can change the contract logic after users have subscribed.
The contract is written in Rust, which eliminates entire classes of vulnerabilities common in other languages: buffer overflows, use-after-free, null pointer dereferences, and integer overflow (Soroban's release profile enables overflow-checks = true).
Each deployed FlowPay contract is initialized with a single token. Supporting multiple tokens (e.g. both XLM and USDC) requires either deploying multiple contracts or refactoring the storage model. Multi-token support is a planned feature.
The charge() trigger relies on an external keeper. If the keeper goes offline, charges will not be processed. This is a liveness concern, not a safety concern — no funds can be lost, but merchants may not receive payments on time. A decentralized keeper network would improve this.
If you discover a security vulnerability in FlowPay, please do not open a public GitHub issue.
Instead, report it privately:
- Email: [leave blank — add your contact email]
- Subject:
[FlowPay Security] Brief description
Please include:
- A description of the vulnerability
- Steps to reproduce
- The potential impact
- Any suggested mitigations
We will acknowledge your report within 48 hours and aim to release a fix within 7 days for critical issues.
We appreciate responsible disclosure and will credit researchers in the release notes unless they prefer to remain anonymous.
Before recommending Mainnet use, we plan to:
- Complete the full feature set (multi-token, TTL management, pause/resume)
- Expand the test suite to 100% branch coverage
- Engage an independent Soroban security auditor
- Publish the audit report publicly
If you are a security researcher interested in auditing FlowPay, please reach out.