Version: 1.0 Date: 2026-02-24 Status: Initial Security Review Reviewer: Security Team
This document presents a comprehensive threat model for the Remitwise smart contract suite deployed on the Stellar Soroban platform. The analysis identifies critical assets, potential threats, existing mitigations, and security gaps across 10 interconnected contracts managing financial operations including remittance allocation, savings goals, bill payments, insurance policies, and family wallet management.
Key Findings:
- 10 critical/high-severity security issues identified
- 8 medium-severity vulnerabilities requiring attention
- 5 low-severity concerns for long-term improvement
- Strong foundation with consistent authorization patterns
- Gaps in cross-contract security, data privacy, and emergency controls
- System Overview
- Asset Identification
- Threat Enumeration
- Existing Mitigations
- Security Gaps
- Threat Scenarios
- Recommendations
- Follow-up Issues
The Remitwise system consists of 10 smart contracts:
Core Financial Contracts:
remittance_split- Allocates incoming remittances by percentagesavings_goals- Goal-based savings with locking mechanismsbill_payments- Recurring and one-time bill trackinginsurance- Micro-insurance policy management
Coordination & Access Control:
family_wallet- Multi-signature family fund managementorchestrator- Cross-contract remittance flow coordinator
Reporting & Utilities:
reporting- Financial health aggregation and analyticsdata_migration- Import/export with checksum validationrate_limiter- (Minimal implementation)analytics- (Minimal implementation)
Incoming Remittance → remittance_split → [savings_goals, bill_payments, insurance]
↓
orchestrator (coordinator)
↓
reporting (aggregator)
| Asset | Location | Value | Protection |
|---|---|---|---|
| User Funds | External (tracked in contracts) | Variable | Owner authorization |
| Savings Balances | savings_goals::current_amount |
Cumulative user savings | Owner-only withdrawal |
| Bill Payment Amounts | bill_payments::amount |
Pending bill amounts | Owner-only payment |
| Insurance Premiums | insurance::monthly_premium |
Recurring premium amounts | Owner-only payment |
| Family Wallet Balances | External (multi-sig controlled) | Shared family funds | Multi-signature approval |
| Asset | Location | Criticality | Protection |
|---|---|---|---|
| Split Percentages | remittance_split::SplitConfig |
HIGH | Owner-only modification |
| Pause Admin | All contracts PAUSE_ADM |
CRITICAL | Self-assignment only |
| Upgrade Admin | All contracts UPG_ADM |
CRITICAL | Self-assignment only |
| Multi-sig Config | family_wallet::MultiSigConfig |
HIGH | Owner/Admin only |
| Contract Addresses | reporting::ContractAddresses |
HIGH | Admin-only configuration |
| Asset | Location | Criticality | Protection |
|---|---|---|---|
| Owner Addresses | All contracts | CRITICAL | Soroban authentication |
| Family Roles | family_wallet::FamilyMember |
HIGH | Owner/Admin management |
| Spending Limits | family_wallet::spending_limit |
MEDIUM | Owner/Admin configuration |
| Nonces | remittance_split, savings_goals |
MEDIUM | Replay protection |
| Asset | Location | Sensitivity | Protection |
|---|---|---|---|
| Financial History | Events, archived records | HIGH | Public blockchain |
| Goal Details | savings_goals::SavingsGoal |
MEDIUM | Owner-only access |
| Bill Details | bill_payments::Bill |
MEDIUM | Owner-only access |
| Policy Details | insurance::InsurancePolicy |
MEDIUM | Owner-only access |
| Audit Logs | All contracts | LOW | Public events |
| Asset | Location | Criticality | Protection |
|---|---|---|---|
| Contract Availability | All contracts | HIGH | Pause mechanisms |
| Storage TTL | Instance storage | MEDIUM | Automatic extension |
| Event Integrity | Blockchain events | MEDIUM | Immutable ledger |
Severity: HIGH
Status: MITIGATED
Description: The reporting contract previously allowed any caller to query sensitive financial data. It now enforces user.require_auth() and validates that the caller matches the user address.
Affected Functions:
get_remittance_summary()get_savings_report()get_bill_compliance_report()get_insurance_coverage_report()
Impact: Privacy violation, information disclosure (Blocked by authorization checks)
Severity: MEDIUM Status: PARTIALLY MITIGATED Description: Orchestrator entry points can become confused-deputy surfaces if they trust caller identity from arguments without constraining direct invocation.
Affected Functions:
orchestrator::execute_remittance_flow()orchestrator::deposit_to_savings()orchestrator::execute_bill_payment()orchestrator::execute_bill_payment_internal()
Attack Vector:
- A non-owner or helper contract forwards a signed bill-payment request for a victim
- The orchestrator trusts the
callerargument without confirming the caller is the stored family wallet owner - Downstream execution proceeds unless another layer blocks it
Impact: Unauthorized fund allocation, state manipulation
Severity: LOW Description: Family wallet role expiry not consistently enforced across all functions.
Affected Functions:
- Various family_wallet functions missing
role_has_expired()checks
Attack Vector:
- Admin role expires but user retains cached permissions
- Expired admin performs privileged operations before expiry is checked
- Unauthorized access window between expiry and enforcement
Impact: Temporary privilege escalation
Severity: MEDIUM Description: Data import operations use nonces but nonce validation may be bypassed if storage is corrupted.
Affected Functions:
remittance_split::import_snapshot()savings_goals::import_goals()
Attack Vector:
- Attacker exports snapshot with valid nonce
- Corrupts nonce storage through separate vulnerability
- Replays old snapshot to revert state changes
Impact: State rollback, data corruption
Severity: LOW
Description: Executed multi-sig transactions stored in EXEC_TXS map but no expiry mechanism.
Affected Functions:
family_wallet::sign_transaction()
Attack Vector:
- Transaction executed and marked in EXEC_TXS
- If EXEC_TXS storage is corrupted or cleared, transaction could be replayed
- Duplicate execution of high-value operations
Impact: Duplicate fund transfers, state inconsistency
Severity: MEDIUM Description: Unbounded maps and audit logs allow attackers to exhaust storage.
Affected Contracts:
- All contracts with Map<u32, Entity> storage
- Audit logs in savings_goals and remittance_split
Attack Vector:
- Attacker creates maximum number of goals/bills/policies
- Performs operations to generate audit log entries
- Storage costs increase, contract becomes expensive to maintain
- Legitimate users unable to create new entities
Impact: Service degradation, increased costs, potential contract abandonment
Severity: MEDIUM Description: Single downstream contract failure causes entire orchestrator flow to revert.
Affected Functions:
orchestrator::execute_remittance_flow()
Attack Vector:
- Attacker pauses or corrupts one downstream contract
- All orchestrator flows fail completely
- No partial success or fallback mechanism
- System-wide denial of service
Impact: Complete service outage, user funds stuck
Severity: LOW Description: Batch operations limited to 50 items but no rate limiting across multiple calls.
Affected Functions:
bill_payments::batch_pay_bills()savings_goals::batch_add_to_goals()
Attack Vector:
- Attacker makes multiple batch calls in rapid succession
- Consumes excessive gas and storage
- Legitimate users experience degraded performance
Impact: Performance degradation, increased costs
Severity: MEDIUM Description: Percentage-based allocation may have rounding errors that accumulate over time.
Affected Functions:
remittance_split::calculate_split()
Attack Vector:
- Attacker sends many small remittances
- Rounding errors accumulate in attacker's favor
- Over time, attacker gains funds from rounding discrepancies
Impact: Financial loss through accumulated rounding errors
Severity: HIGH
Status: MITIGATED
Description: Emergency mode previously allowed unlimited transfers. It now enforces a strict EM_LAST timestamp cooldown and limits amounts based on EmergencyConfig.
Affected Functions:
family_wallet::execute_emergency_transfer_now()family_wallet::set_emergency_mode()
Impact: Complete fund loss (Blocked by cooldown and amount limits)
Severity: LOW Description: Spending limit of 0 means unlimited, no way to enforce true zero spending.
Affected Functions:
family_wallet::check_spending_limit()
Attack Vector:
- Admin sets member spending limit to 0 intending to block spending
- Member has unlimited spending due to 0 = unlimited logic
- Unintended fund access
Impact: Unauthorized spending
Severity: MEDIUM Description: Data migration uses simple checksum vulnerable to collision attacks.
Affected Functions:
data_migration::ExportSnapshot::verify_checksum()
Attack Vector:
- Attacker exports legitimate snapshot
- Modifies payload to malicious data
- Crafts collision to match original checksum
- Imports corrupted data that passes validation
Impact: Data corruption, state manipulation
Severity: LOW Description: Archived records use compressed structs losing original data fields.
Affected Functions:
bill_payments::archive_paid_bills()savings_goals::archive_goal()insurance::archive_policy()
Attack Vector:
- User archives entity
- Attempts to restore archived entity
- Original fields (due_date, target_date, etc.) are lost
- Restored entity has incomplete data
Impact: Data loss, operational confusion
Severity: MEDIUM Description: Savings goals uses both persistent and instance storage with different TTLs.
Affected Contracts:
savings_goals(GOALS in persistent, pause state in instance)
Attack Vector:
- Instance storage TTL expires
- Pause state lost but goals remain
- Contract state becomes inconsistent
- Paused contract appears unpaused
Impact: State inconsistency, security control bypass
Severity: HIGH Status: MITIGATED Description: Orchestrator makes multiple cross-contract calls; reentrancy protection is now enforced via an execution state lock.
Affected Functions:
orchestrator::execute_remittance_flow()orchestrator::execute_savings_deposit()orchestrator::execute_bill_payment()orchestrator::execute_insurance_payment()- All cross-contract client calls
Attack Vector:
- Attacker deploys malicious contract as downstream dependency
- Malicious contract calls back to orchestrator during execution
- Orchestrator state is modified mid-execution
- Inconsistent state or duplicate operations
Mitigation (Implemented):
ExecutionStateenum (Idle/Executing) stored in instance storage under keyEXEC_STacquire_execution_lock()checks state at entry; returnsReentrancyDetected(error code 10) if already executingrelease_execution_lock()unconditionally resets state toIdleon all exit paths (success and failure)- All four public entry points are guarded:
execute_savings_deposit,execute_bill_payment,execute_insurance_payment,execute_remittance_flow - Lock release is guaranteed via closure pattern that captures the execution body, ensuring release even on error paths
get_execution_state()public query allows monitoring and verification
Impact: State corruption, duplicate fund allocation, financial loss — now blocked by reentrancy guard
Severity: MEDIUM Description: Reporting contract doesn't validate configured addresses are actual contracts.
Affected Functions:
reporting::configure_addresses()
Attack Vector:
- Admin configures invalid or malicious contract addresses
- Reporting queries fail silently or return malicious data
- Users receive incorrect financial reports
- Decisions made based on false data
Impact: Data corruption, operational failures, financial mismanagement
Severity: MEDIUM Description: Single pause admin with no backup or recovery mechanism.
Affected Contracts:
- All contracts with pause functionality
Attack Vector:
- Pause admin key is lost or compromised
- No backup admin can pause contract in emergency
- Malicious activity continues unchecked
- Or legitimate operations blocked if compromised admin pauses
Impact: Loss of emergency controls, potential for extended attacks
Severity: LOW Description: Upgrade admin can change version number but no actual upgrade mechanism exists.
Affected Functions:
set_version()in all contracts
Attack Vector:
- Upgrade admin sets arbitrary version number
- Version mismatch causes compatibility issues
- Data migration fails due to version incompatibility
Impact: Operational confusion, migration failures
Severity: LOW Description: Goal names, bill names, policy names have no length limits.
Affected Functions:
create_goal(),create_bill(),create_policy()
Attack Vector:
- Attacker creates entities with extremely long names
- Storage costs increase dramatically
- Query operations become expensive
- Potential for storage exhaustion
Impact: Storage bloat, increased costs, performance degradation
Severity: LOW Description: Tags validated for length but not content, could contain malicious strings.
Affected Functions:
add_tags_to_goal(),add_tags_to_bill(),add_tags_to_policy()
Attack Vector:
- Attacker adds tags with special characters or malicious content
- Tags stored in contract state
- Potential for injection attacks in off-chain systems reading tags
Impact: Data corruption, potential injection attacks in integrations
Severity: LOW Description: No maximum limits on amounts, could cause calculation issues.
Affected Functions:
- All functions accepting i128 amounts
Attack Vector:
- Attacker creates goal/bill/policy with i128::MAX amount
- Arithmetic operations overflow or behave unexpectedly
- Contract state becomes corrupted
Impact: Integer overflow, calculation errors, state corruption
Severity: MEDIUM Description: Events include full amounts, dates, and addresses visible to all.
Affected Contracts:
- All contracts emitting financial events
Attack Vector:
- Attacker monitors blockchain events
- Builds complete financial profile of users
- Uses information for targeted attacks or social engineering
Impact: Privacy violation, information disclosure
Severity: LOW Description: Audit logs grow without limit, no cleanup mechanism.
Affected Contracts:
savings_goals,remittance_split
Attack Vector:
- Attacker performs many operations to generate audit entries
- Audit log grows unbounded
- Storage costs increase
- Contract becomes expensive to maintain
Impact: Storage bloat, increased costs
Severity: MEDIUM Description: Each contract has independent pause state, no global coordination.
Affected Contracts:
- All contracts with pause functionality
Attack Vector:
- Admin pauses some contracts but not others
- Orchestrator continues operating with partially paused system
- Inconsistent state across contracts
- Operations fail unpredictably
Impact: Operational confusion, inconsistent security posture
Severity: LOW Description: Pause events don't include reason or context.
Affected Contracts:
- All contracts with pause functionality
Attack Vector:
- Contract is paused for unknown reason
- Operators unable to determine cause
- Delayed response to security incidents
- Potential for unnecessary downtime
Impact: Operational inefficiency, delayed incident response
✅ Soroban Authentication
- All state-modifying functions call
caller.require_auth() - Cryptographic signature verification at protocol level
- Prevents unauthorized function calls
✅ Owner-Based Access Control
- Each entity (goal, bill, policy) has owner field
- Operations verify
caller == ownerbefore execution - Prevents cross-user manipulation
✅ Role-Based Hierarchy
- Family wallet implements Owner > Admin > Member hierarchy
- Role checks enforce privilege separation
- Prevents unauthorized privilege escalation
✅ Multi-Signature Requirements
- High-value operations require multiple signatures
- Configurable threshold per transaction type
- Prevents single-point-of-failure for critical operations
✅ Checked Arithmetic
- Uses
checked_add(),checked_sub(),checked_mul() - Prevents integer overflow/underflow
- Transactions revert on arithmetic errors
✅ Amount Validation
- All fund operations validate
amount > 0 - Prevents negative amounts and zero-value operations
- Ensures meaningful transactions only
✅ Percentage Validation
- Remittance split validates percentages sum to 100
- Prevents invalid allocation configurations
- Ensures complete fund distribution
✅ Nonce-Based Replay Protection
- Import operations use incrementing nonces
- Prevents replay of old snapshots
- Ensures data import idempotency
✅ TTL Management
- Automatic instance storage extension (518,400 ledgers ≈ 6 days)
- Archive storage with longer TTL (2,592,000 ledgers ≈ 30 days)
- Prevents premature data expiration
✅ Atomic Operations
- All state changes are atomic
- Transaction reverts on any failure
- Prevents partial state updates
✅ Data Archival
- Completed entities moved to compressed archive storage
- Reduces active storage costs
- Maintains historical records
✅ Event Logging
- All state changes emit events
- Provides complete audit trail
- Enables off-chain monitoring and analytics
✅ Pause Mechanisms
- Global pause stops all operations
- Function-level pause for granular control
- Emergency pause all for rapid response
✅ Scheduled Unpause
- Bill payments supports time-delayed unpause
- Prevents indefinite pause states
- Enables automated recovery
✅ Emergency Mode
- Family wallet supports emergency transfers
- Bypasses multi-sig for urgent situations
- Enables rapid fund recovery
✅ Frequency Validation
- Recurring bills validate
frequency_days > 0 - Prevents invalid recurring schedules
- Ensures meaningful recurrence
✅ Tag Format Validation
- Tags validated for length (1-32 characters)
- Prevents empty or excessively long tags
- Ensures consistent tag format
✅ Threshold Validation
- Multi-sig threshold validated against signer count
- Prevents impossible threshold configurations
- Ensures achievable approval requirements
✅ Type-Safe Client Calls
- Uses generated Soroban clients
- Compile-time type checking
- Prevents parameter mismatches
✅ Error Propagation
- Downstream failures cause transaction revert
- Maintains atomicity across contracts
- Prevents inconsistent state
✅ Gas Estimation
- Cross-contract calls include gas estimates
- Helps prevent out-of-gas failures
- Enables cost planning
❌ No Authorization in Reporting Queries
- Gap: Reporting contract allows any caller to query any user's financial data
- Missing Control: Caller verification or access control lists
- Risk: Complete privacy violation, information disclosure
- Recommendation: Add
caller.require_auth()and verifycaller == useror implement ACL
✅ Reentrancy Protection (Implemented)
- Previously: Orchestrator made multiple cross-contract calls without reentrancy guards
- Mitigation:
ExecutionStatelock in instance storage guards all public entry points (execute_savings_deposit,execute_bill_payment,execute_insurance_payment,execute_remittance_flow). Reentrant calls receiveReentrancyDetected(error 10). Lock release is guaranteed on both success and error paths via closure-based execution pattern. - Verification: Comprehensive tests confirm guard blocks concurrent access, releases on success/failure, and supports sequential operations
❌ Emergency Mode Lacks Rate Limiting
- Gap: Emergency transfers not rate-limited or cooldown-enforced
- Missing Control: Transfer frequency limits, cooldown enforcement
- Risk: Rapid fund drain in emergency mode
- Recommendation: Enforce cooldown between emergency transfers, add transfer count limits
❌ Weak Checksum Validation
- Gap: Data migration uses simple checksum, not cryptographic hash
- Missing Control: SHA-256 or similar cryptographic hash
- Risk: Collision attacks, corrupted data passing validation
- Recommendation: Replace with SHA-256 or BLAKE2b
❌ No Balance Verification
- Gap: Contracts track balances but don't verify actual token balances
- Missing Control: Token balance queries and reconciliation
- Risk: State-balance mismatch, double-spending
- Recommendation: Add balance verification functions, periodic reconciliation
❌ Pause State Not Synchronized
- Gap: Each contract has independent pause state
- Missing Control: Global pause coordinator or cross-contract pause propagation
- Risk: Partial system pause, inconsistent security posture
- Recommendation: Implement global pause mechanism or pause state synchronization
❌ No Storage Bounds
- Gap: Maps grow unbounded, no limits on entity creation
- Missing Control: Maximum entity counts, storage quotas
- Risk: Storage bloat, DoS through excessive creation
- Recommendation: Implement per-user entity limits, storage quotas
❌ Mixed Storage Types
- Gap: Savings goals uses both persistent and instance storage
- Missing Control: Consistent storage type usage
- Risk: TTL mismatch, state inconsistency
- Recommendation: Standardize on single storage type (persistent recommended)
❌ No Contract Address Validation
- Gap: Reporting contract doesn't validate configured addresses
- Missing Control: Contract existence checks, interface validation
- Risk: Silent failures, malicious contract injection
- Recommendation: Validate addresses are contracts, verify interfaces
❌ Role Expiry Not Consistently Enforced
- Gap: Some functions don't check role expiry
- Missing Control: Consistent expiry checks across all role-based functions
- Risk: Expired roles retain temporary access
- Recommendation: Add expiry check to all role-based functions
❌ No Upgrade Mechanism
- Gap: Version tracked but no actual upgrade function
- Missing Control: Contract upgrade or migration mechanism
- Risk: Stuck with bugs, no security patch path
- Recommendation: Implement upgrade mechanism or document migration process
❌ Insufficient Error Differentiation
- Gap: Many functions use panic!() instead of Result types
- Missing Control: Structured error types, error codes
- Risk: Difficult debugging, poor error handling
- Recommendation: Use Result types consistently, define error enums
❌ No Input Bounds
- Gap: String inputs, amounts have no maximum limits
- Missing Control: Maximum length/value validation
- Risk: Storage bloat, calculation issues
- Recommendation: Add maximum limits for strings and amounts
❌ Audit Log Unbounded
- Gap: Audit logs grow without limit
- Missing Control: Automatic cleanup, log rotation
- Risk: Storage bloat, increased costs
- Recommendation: Implement automatic cleanup or log size limits
❌ No Pause Reason Tracking
- Gap: Pause events don't include reason
- Missing Control: Reason field in pause events
- Risk: Operational confusion, delayed incident response
- Recommendation: Add reason parameter to pause functions
Attacker Profile: External observer, no special privileges
Attack Steps:
- Attacker identifies target user address from public transactions
- Calls
reporting::get_remittance_summary()with target address - Calls
reporting::get_savings_report()to get savings goals and balances - Calls
reporting::get_bill_compliance_report()to get bill payment history - Calls
reporting::get_insurance_coverage_report()to get policy details - Builds complete financial profile of target user
Impact:
- Complete privacy violation
- Sensitive financial data exposed
- Potential for targeted phishing or social engineering
- Regulatory compliance issues (GDPR, financial privacy laws)
Likelihood: HIGH (trivial to execute, no barriers)
Mitigation Status: ❌ Not mitigated
Attacker Profile: Compromised Owner/Admin account
Attack Steps:
- Attacker gains access to Owner/Admin private key (phishing, malware, etc.)
- Calls
family_wallet::set_emergency_mode(true)to activate emergency mode - Rapidly calls
execute_emergency_transfer_now()multiple times - Transfers all family wallet funds to attacker-controlled addresses
- Deactivates emergency mode to cover tracks
Impact:
- Complete loss of family wallet funds
- No multi-sig protection in emergency mode
- No cooldown enforcement allows rapid drain
- Difficult to detect and respond before funds are gone
Likelihood: MEDIUM (requires account compromise but high impact)
Mitigation Status:
Attacker Profile: Malicious contract deployer
Attack Steps:
- Attacker deploys malicious contract implementing savings/bills/insurance interface
- Attacker configures orchestrator to use malicious contract as downstream dependency
- Attacker calls
orchestrator::execute_remittance_flow() - Malicious contract receives call, calls back to orchestrator mid-execution
- Orchestrator state is modified during execution
- Duplicate allocations or state corruption occurs
Impact:
- State corruption across multiple contracts
- Duplicate fund allocations
- Financial loss through double-spending
- System-wide inconsistency
Likelihood: LOW (requires orchestrator misconfiguration but high impact)
Mitigation Status: ❌ Not mitigated
Attacker Profile: Malicious user with minimal funds
Attack Steps:
- Attacker creates maximum number of savings goals (no limit)
- Attacker creates maximum number of bills (no limit)
- Attacker creates maximum number of insurance policies (no limit)
- Attacker performs operations to generate audit log entries
- Storage costs increase dramatically
- Legitimate users unable to create new entities due to storage exhaustion
Impact:
- Service degradation for all users
- Increased operational costs
- Potential contract abandonment due to unsustainable costs
- Denial of service for new entity creation
Likelihood: MEDIUM (requires sustained effort but achievable)
Mitigation Status:
Attacker Profile: Malicious data exporter
Attack Steps:
- Attacker exports legitimate snapshot from contract
- Attacker modifies payload to inject malicious data
- Attacker crafts checksum collision to match original
- Attacker imports corrupted snapshot
- Contract accepts corrupted data due to checksum match
- Contract state becomes corrupted
Impact:
- Data corruption across contract state
- Incorrect financial calculations
- Loss of data integrity
- Potential for financial loss
Likelihood: LOW (requires cryptographic expertise but possible)
Mitigation Status:
Attacker Profile: Malicious actor exploiting operational confusion
Attack Steps:
- Attacker identifies security issue in one contract
- Admin pauses affected contract
- Orchestrator continues operating with other contracts
- Attacker exploits unpaused contracts to manipulate state
- When paused contract is unpaused, state is inconsistent
- System-wide confusion and potential financial loss
Impact:
- Inconsistent security posture
- Partial system protection only
- Operational confusion
- Potential for exploitation during partial pause
Likelihood: MEDIUM (depends on operational procedures)
Mitigation Status: ❌ Not mitigated
Issue: T-UA-01 Action: Implement caller verification in all reporting query functions
pub fn get_remittance_summary(
env: Env,
caller: Address,
user: Address,
// ... other params
) -> RemittanceSummary {
caller.require_auth();
if caller != user {
// Check if caller has permission (ACL, admin, etc.)
panic!("Unauthorized access to user data");
}
// ... existing logic
}Timeline: Immediate (before mainnet deployment) Effort: Low (1-2 days)
Issue: T-RE-01 Action: Reentrancy guard implemented in orchestrator
Implementation details:
ExecutionStateenum (Idle/Executing) stored underEXEC_STkey in instance storageacquire_execution_lock()atomically checks and sets state; returnsReentrancyDetected(error 10) on conflictrelease_execution_lock()resets toIdleunconditionally- All four public entry points guarded:
execute_savings_deposit,execute_bill_payment,execute_insurance_payment,execute_remittance_flow - Closure-based execution pattern ensures lock release on all code paths
get_execution_state()public query for monitoring- 15+ tests covering: initial state, lock release on success/failure, reentrant call rejection, sequential execution, recovery after failure
Issue: T-EC-02 Action: Enforce cooldown and transfer limits in emergency mode
pub fn execute_emergency_transfer_now(...) -> u64 {
// Check cooldown
let last_transfer: u64 = env.storage().instance().get(&symbol_short!("EM_LAST")).unwrap_or(0);
let em_config: EmergencyConfig = env.storage().instance().get(&symbol_short!("EM_CONF")).expect("Emergency config not set");
let current_time = env.ledger().timestamp();
if current_time < last_transfer + em_config.cooldown {
panic!("Emergency transfer cooldown not elapsed");
}
// Update last transfer time
env.storage().instance().set(&symbol_short!("EM_LAST"), ¤t_time);
// ... existing logic
}Timeline: Immediate (before mainnet deployment) Effort: Low (1-2 days)
Issue: T-DI-01 Action: Use SHA-256 instead of simple checksum
use sha2::{Sha256, Digest};
impl ExportSnapshot {
pub fn compute_checksum(&self) -> String {
let mut hasher = Sha256::new();
hasher.update(serde_json::to_vec(&self.payload).expect("payload must be serializable"));
hex::encode(hasher.finalize())
}
}Timeline: 1-2 weeks Effort: Low (already using sha2 crate)
Issue: T-DOS-01 Action: Add per-user entity limits
const MAX_GOALS_PER_USER: u32 = 100;
const MAX_BILLS_PER_USER: u32 = 200;
const MAX_POLICIES_PER_USER: u32 = 50;
pub fn create_goal(...) -> u32 {
// ... existing auth and validation
// Count existing goals for owner
let mut count = 0u32;
for (_, goal) in goals.iter() {
if goal.owner == owner {
count += 1;
}
}
if count >= MAX_GOALS_PER_USER {
panic!("Maximum goals per user exceeded");
}
// ... existing logic
}Timeline: 2-3 weeks Effort: Medium (requires testing across all contracts)
Issue: T-DI-03 Action: Convert all storage to persistent type
// Change from:
env.storage().instance().get(&symbol_short!("GOALS"))
// To:
env.storage().persistent().get(&symbol_short!("GOALS"))Timeline: 2-3 weeks Effort: Medium (requires careful migration and testing)
Issue: T-RE-02 Action: Validate configured addresses are contracts
pub fn configure_addresses(
env: Env,
caller: Address,
remittance_split: Address,
// ... other addresses
) -> bool {
caller.require_auth();
// Validate addresses are contracts (attempt to call a standard function)
// This will panic if address is not a contract
let split_client = RemittanceSplitClient::new(&env, &remittance_split);
let _ = split_client.get_split(); // Verify contract responds
// ... existing logic
}Timeline: 1-2 weeks Effort: Low
Issue: T-PC-01 Action: Create pause coordinator contract
#[contract]
pub struct PauseCoordinator;
#[contractimpl]
impl PauseCoordinator {
pub fn pause_all(env: Env, admin: Address, reason: String) {
admin.require_auth();
// Pause all registered contracts
let contracts: Vec<Address> = env.storage().instance().get(&symbol_short!("CONTRACTS")).unwrap();
for contract_addr in contracts.iter() {
// Call pause on each contract
// Store reason for audit
}
}
}Timeline: 4-6 weeks Effort: High (new contract, integration testing)
Issue: T-DI-02 Action: Implement balance reconciliation functions
pub fn verify_balances(env: Env, token: Address) -> bool {
let token_client = TokenClient::new(&env, &token);
let contract_balance = token_client.balance(&env.current_contract_address());
// Calculate expected balance from contract state
let expected_balance = Self::calculate_total_balances(&env);
if contract_balance != expected_balance {
env.events().publish(
(symbol_short!("balance"), symbol_short!("mismatch")),
(contract_balance, expected_balance),
);
return false;
}
true
}Timeline: 3-4 weeks Effort: Medium
Issue: T-EV-02 Action: Add automatic audit log rotation
const MAX_AUDIT_ENTRIES: u32 = 1000;
fn append_audit(env: &Env, operation: Symbol, caller: &Address, success: bool) {
let mut audit: Vec<AuditEntry> = env.storage().instance().get(&symbol_short!("AUDIT")).unwrap_or_else(|| Vec::new(env));
// If at max, remove oldest entry
if audit.len() >= MAX_AUDIT_ENTRIES {
audit.remove(0);
}
audit.push_back(AuditEntry {
operation,
caller: caller.clone(),
timestamp: env.ledger().timestamp(),
success,
});
env.storage().instance().set(&symbol_short!("AUDIT"), &audit);
}Timeline: 2-3 weeks Effort: Low
Issue: T-PE-02 Action: Design and implement contract upgrade pattern
Timeline: 8-12 weeks Effort: High (requires architecture design)
Issue: T-EV-01 Action: Implement data encryption or access control for sensitive events
Timeline: 6-8 weeks Effort: High (requires protocol-level changes)
Issue: T-IV-02 Action: Replace panic!() with Result types across all contracts
Timeline: 6-8 weeks Effort: High (requires extensive refactoring)
The following security issues have been created for tracking and implementation:
-
[SECURITY-001] Add Authorization to Reporting Contract Queries
- Severity: HIGH
- Component: reporting contract
- Description: Implement caller verification in all query functions to prevent unauthorized access to sensitive financial data
- Acceptance Criteria:
- All reporting query functions require caller authentication
- Caller must be the user or have explicit permission
- Add access control list (ACL) support for shared access
- Update tests to verify authorization checks
- Estimated Effort: 2-3 days
-
[SECURITY-002] Implement Reentrancy Protection in Orchestrator ✅ COMPLETED
- Severity: HIGH
- Component: orchestrator contract
- Description: Reentrancy guard implemented using
ExecutionStateenum in instance storage - Acceptance Criteria: All met:
- ✅ Reentrancy guard implemented using
ExecutionState(Idle/Executing) storage flag - ✅ All four public entry points protected (
execute_savings_deposit,execute_bill_payment,execute_insurance_payment,execute_remittance_flow) - ✅ 15+ tests verify reentrancy attempts are blocked and lock releases correctly
- ✅ Gas cost: ~500 gas for acquire + ~300 gas for release (~800 gas overhead per call)
- ✅ Reentrancy guard implemented using
-
[SECURITY-003] Add Rate Limiting to Emergency Transfers
- Severity: HIGH
- Component: family_wallet contract
- Description: Enforce cooldown and transfer limits in emergency mode to prevent rapid fund drain
- Acceptance Criteria:
- Cooldown enforced between emergency transfers
- Maximum transfer count per time period implemented
- Emergency config includes rate limit parameters
- Tests verify rate limiting works correctly
- Estimated Effort: 2-3 days
-
[SECURITY-004] Replace Checksum with Cryptographic Hash
- Severity: MEDIUM
- Component: data_migration module
- Description: Use SHA-256 instead of simple checksum for data integrity verification
- Acceptance Criteria:
- SHA-256 hash replaces simple checksum
- Backward compatibility maintained for existing snapshots
- Tests verify collision resistance
- Documentation updated
- Estimated Effort: 1-2 days
-
[SECURITY-005] Implement Storage Bounds and Entity Limits
- Severity: MEDIUM
- Component: All contracts (savings_goals, bill_payments, insurance)
- Description: Add per-user limits on entity creation to prevent storage bloat DoS
- Acceptance Criteria:
- Maximum entities per user defined (goals: 100, bills: 200, policies: 50)
- Creation functions enforce limits
- Error messages indicate limit reached
- Tests verify limits are enforced
- Admin function to adjust limits if needed
- Estimated Effort: 3-4 days
-
[SECURITY-006] Standardize Storage Type to Persistent
- Severity: MEDIUM
- Component: savings_goals contract
- Description: Convert all storage to persistent type to prevent TTL mismatch issues
- Acceptance Criteria:
- All instance storage converted to persistent
- TTL management updated for persistent storage
- Migration path documented
- Tests verify storage consistency
- Estimated Effort: 2-3 days
-
[SECURITY-007] Add Contract Address Validation
- Severity: MEDIUM
- Component: reporting contract
- Description: Validate configured contract addresses are actual contracts
- Acceptance Criteria:
- Address validation in configure_addresses()
- Test call to verify contract responds
- Error handling for invalid addresses
- Tests verify validation works
- Estimated Effort: 1-2 days
-
[SECURITY-008] Enforce Role Expiry Consistently
- Severity: MEDIUM
- Component: family_wallet contract
- Description: Add role expiry checks to all role-based functions
- Acceptance Criteria:
- All role-based functions check expiry
- Expired roles immediately lose access
- Tests verify expiry enforcement
- Documentation updated
- Estimated Effort: 2-3 days
-
[SECURITY-009] Implement Global Pause Coordinator
- Severity: MEDIUM
- Component: New pause_coordinator contract
- Description: Create coordinator contract to synchronize pause state across all contracts
- Acceptance Criteria:
- New pause coordinator contract deployed
- All contracts register with coordinator
- Pause all function implemented
- Pause reason tracking added
- Tests verify synchronized pause
- Estimated Effort: 4-6 weeks
-
[SECURITY-010] Add Balance Verification Functions
- Severity: MEDIUM
- Component: All contracts handling funds
- Description: Implement balance reconciliation to detect state-balance mismatches
- Acceptance Criteria:
- Balance verification function implemented
- Periodic reconciliation mechanism
- Mismatch events emitted
- Admin dashboard shows balance status
- Tests verify reconciliation works
- Estimated Effort: 3-4 weeks
-
[SECURITY-011] Implement Audit Log Cleanup
- Severity: LOW
- Component: savings_goals, remittance_split contracts
- Description: Add automatic audit log rotation to prevent unbounded growth
- Acceptance Criteria:
- Maximum audit entries defined (1000)
- Oldest entries removed when limit reached
- Archive mechanism for old logs
- Tests verify rotation works
- Estimated Effort: 2-3 weeks
-
[SECURITY-012] Add Input Bounds Validation
- Severity: LOW
- Component: All contracts
- Description: Implement maximum limits for string inputs and amounts
- Acceptance Criteria:
- Maximum string length defined (256 characters)
- Maximum amount defined (i128::MAX / 2)
- Validation in all input functions
- Tests verify bounds enforcement
- Estimated Effort: 2-3 weeks
-
[SECURITY-013] Implement Contract Upgrade Mechanism
- Severity: LOW
- Component: All contracts
- Description: Design and implement upgrade pattern for deployed contracts
- Acceptance Criteria:
- Upgrade mechanism designed
- Migration path documented
- Backward compatibility maintained
- Tests verify upgrade works
- Estimated Effort: 8-12 weeks
- Fuzz Testing: Test arithmetic operations with random inputs to detect overflow/underflow
- Reentrancy Testing: Attempt reentrancy attacks on all cross-contract calls
- Authorization Testing: Verify all functions properly check caller permissions
- Storage Testing: Test TTL expiration and storage consistency
- Pause Testing: Verify pause state synchronization across contracts
- Cross-Contract Flows: Test complete remittance flows with failures at each step
- Multi-Sig Workflows: Test all multi-sig transaction types with various signer combinations
- Emergency Scenarios: Test emergency mode activation and fund recovery
- Data Migration: Test import/export with corrupted data and invalid checksums
- Storage Bloat: Test with maximum entities to measure storage costs
- Gas Costs: Measure gas costs for all operations, especially cross-contract calls
- Pagination: Test query functions with large result sets
- Batch Operations: Test batch functions with maximum batch sizes
- Event Monitoring: Monitor all events for suspicious patterns
- Balance Monitoring: Track contract balances and detect mismatches
- Pause State Monitoring: Alert on pause state changes
- Storage Monitoring: Track storage usage and growth rates
- Authorization Failures: Monitor failed authorization attempts
- Detection: Automated monitoring detects anomaly
- Assessment: Security team evaluates severity and impact
- Containment: Pause affected contracts if necessary
- Investigation: Analyze events and state to determine root cause
- Remediation: Deploy fixes or migrate to new contracts
- Recovery: Restore normal operations and verify integrity
- Post-Mortem: Document incident and update security controls
- GDPR: User financial data is publicly visible on blockchain
- Recommendation: Implement off-chain encryption or privacy layer
- Action: Add privacy notice to user documentation
- AML/KYC: No identity verification in smart contracts
- Recommendation: Implement off-chain compliance layer
- Action: Document compliance requirements for integrators
- Requirement: Complete audit trail of all financial operations
- Status: ✅ Implemented via events and audit logs
- Recommendation: Ensure audit logs are preserved and accessible
The Remitwise smart contract suite demonstrates a solid security foundation with consistent authorization patterns, comprehensive event logging, and robust pause mechanisms. However, several critical gaps require immediate attention before mainnet deployment:
Critical Actions Required:
- Add authorization to reporting contract queries
- Implement reentrancy protection in orchestrator
- Add rate limiting to emergency transfers
High-Priority Improvements: 4. Replace weak checksum with cryptographic hash 5. Implement storage bounds and entity limits 6. Standardize storage types 7. Add contract address validation 8. Enforce role expiry consistently
Ongoing Security Practices:
- Regular security audits
- Continuous monitoring and alerting
- Incident response planning
- User education on security best practices
By addressing these issues systematically, the Remitwise platform can achieve a strong security posture suitable for production deployment.
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-02-24 | Security Team | Initial threat model |
- Soroban Security Best Practices
- Smart Contract Security Verification Standard
- OWASP Smart Contract Top 10
- Remitwise Architecture Documentation (ARCHITECTURE.md)
An attacker who captures a valid signed orchestrator command payload can resubmit it to trigger the same operation multiple times (replay attack).
All single-operation entry points (execute_savings_deposit, execute_bill_payment, execute_insurance_payment) now require a caller-supplied nonce: u64 parameter.
The nonce is bound to a composite key of (caller, command_type, nonce) stored in persistent contract storage. Once consumed, the key is permanently recorded and any attempt to reuse it returns OrchestratorError::NonceAlreadyUsed.
- Caller-scoped: the same nonce value is valid for different callers.
- Command-scoped: the same nonce value is valid across different command types.
- Permanent: consumed nonces never expire — there is no time window for replay.
- Atomic: nonce consumption happens before any state changes; a failed call does not consume the nonce if it fails before the consume step is reached; if it fails after, the nonce is consumed and the operation must be retried with a fresh nonce.
| Code | Name | Description |
|---|---|---|
| 11 | SelfReferenceNotAllowed | A contract address references the orchestrator itself |
| 12 | DuplicateContractAddress | Two or more contract addresses are identical |
| 14 | NonceAlreadyUsed | Nonce has already been consumed for this caller/command pair |
Six dedicated nonce tests cover: replay rejection per command type, nonce isolation per caller, nonce isolation across command types, and sequential unique nonce acceptance.
orchestrator::execute_bill_payment() accepted a user-supplied caller address and required that address to authorize, but it did not explicitly reject execution forwarded through another contract. That left room for confused-deputy behavior where a non-owner caller could attempt to relay a victim-approved authorization path.
- The only trusted principal for
execute_bill_payment()is the family wallet owner returned byfamily_wallet_addr. - The authenticated
callermust match that stored owner before bill execution proceeds. - The downstream
bill_paymentscontract still enforces bill ownership, but the orchestrator now rejects forwarded execution before reaching that layer.
- Direct owner calls are allowed.
- Non-owner forwarding through helper or proxy contracts is rejected because the forwarded
callerstill must equal the stored family wallet owner. - No general delegation model is supported for
execute_bill_payment().
Delegation is intentionally unsupported for this entry point. A non-owner cannot self-assert authority by:
- passing an owner address in
caller - forwarding a call as a non-owner through another contract
- replaying a previously authorized payload with the same nonce
- Non-owner forwarding: blocked by requiring
caller.require_auth()andcaller == family_wallet.get_owner(). - Argument spoofing: blocked because supplying another user's address without that user's direct authorization fails authentication.
- Unauthorized delegated execution: blocked because there is no delegate allowlist and every caller must match the stored owner address.
bill_payments::pay_bill()remains the source of truth for bill ownership and must continue rejecting non-owners.- The orchestrator does not support approved delegates for bill payment execution; adding one in the future would require explicit stored authorization state and new tests.
- Replay safety depends on preserving the caller-scoped nonce record in persistent storage.