This document presents a comprehensive security audit of the donation flow in the Stellar Micro-Donation API. The audit identified 12 critical vulnerabilities, 8 high-priority issues, and 15 medium-priority concerns that require immediate attention.
- π΄ Critical: 12 issues (Immediate action required)
- π High: 8 issues (Action required within 1 week)
- π‘ Medium: 15 issues (Action required within 1 month)
- π’ Low: 7 issues (Monitor and improve)
Severity: π΄ CRITICAL
Location: src/routes/donation.js:48
Issue: The /donations/send endpoint has NO authentication or authorization checks. Anyone can send donations from any wallet if they know the user ID.
router.post('/send', async (req, res) => { // β NO AUTH CHECK
const { senderId, receiverId, amount, memo } = req.body;Attack Vector:
- Attacker can drain any wallet by knowing the user ID
- No rate limiting on this endpoint
- No permission validation
Impact: Complete loss of funds, unauthorized transactions
Recommendation:
router.post('/send',
checkPermission(PERMISSIONS.DONATIONS_CREATE),
checkOwnership('senderId'), // Verify user owns the sender wallet
async (req, res) => {Severity: π΄ CRITICAL
Location: src/routes/donation.js:93
Issue: The system stores encrypted secret keys and decrypts them server-side for transactions. This is a custodial model with severe security implications.
const secret = encryption.decrypt(sender.encryptedSecret);Attack Vectors:
- If encryption key is compromised, all wallets are compromised
- Server has access to all private keys
- Single point of failure
- Insider threat risk
Impact: Complete loss of all user funds if server is compromised
Recommendation:
- Immediate: Implement non-custodial model where users sign transactions client-side
- Short-term: Use HSM (Hardware Security Module) for key storage
- Long-term: Implement multi-signature wallets
- Add key rotation mechanism
- Implement audit logging for all key access
Severity: π΄ CRITICAL
Location: src/routes/donation.js:82-83
Issue: While parameterized queries are used, there's no input sanitization before database operations.
const sender = await Database.get('SELECT * FROM users WHERE id = ?', [senderId]);Attack Vector:
- If
senderIdcontains malicious input and Database.get doesn't properly escape - Potential for second-order SQL injection
Recommendation:
- Add input validation middleware
- Use ORM with built-in protection
- Implement input sanitization layer
- Add database query logging
Severity: π΄ CRITICAL
Location: src/routes/donation.js:149
Issue: Idempotency key is required but NOT checked for duplicates. This allows duplicate transactions.
if (!idempotencyKey) {
return res.status(400).json({ /* error */ });
}
// β NO CHECK IF KEY WAS ALREADY USEDAttack Vector:
- Replay attacks with same idempotency key
- Double-spending
- Accidental duplicate donations
Impact: Financial loss, duplicate charges
Recommendation:
// Check if idempotency key was already used
const existing = await Transaction.getByIdempotencyKey(idempotencyKey);
if (existing) {
return res.status(200).json({
success: true,
data: existing,
message: 'Transaction already processed'
});
}Severity: π΄ CRITICAL
Location: All donation endpoints
Issue: No rate limiting implemented on any donation endpoint.
Attack Vectors:
- DDoS attacks
- Brute force attacks
- Resource exhaustion
- Spam donations
Recommendation:
const rateLimit = require('express-rate-limit');
const donationLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many donation requests, please try again later'
});
router.post('/donations', donationLimiter, checkPermission(...), ...);Severity: π΄ CRITICAL
Location: src/routes/donation.js:93-105
Issue: No balance check before attempting Stellar transaction.
// β NO BALANCE CHECK
const stellarResult = await stellarService.sendDonation({
sourceSecret: secret,
destinationPublic: receiver.publicKey,
amount: amount,
memo: memo
});Attack Vector:
- Failed transactions still recorded
- Wasted network fees
- Poor user experience
Recommendation:
// Check balance before transaction
const balance = await stellarService.getBalance(sender.publicKey);
if (parseFloat(balance.balance) < parseFloat(amount) + 0.00001) { // Include fee
return res.status(400).json({
success: false,
error: 'Insufficient balance'
});
}Severity: π΄ CRITICAL
Location: src/routes/donation.js:107-122
Issue: Database and Stellar transactions are not atomic. If one fails, the other may succeed.
// Stellar transaction
const stellarResult = await stellarService.sendDonation(...);
// Database record (separate operation)
const dbResult = await Database.run(...);Attack Vector:
- Money sent but not recorded in database
- Database record created but Stellar transaction fails
- Inconsistent state
Recommendation:
// Use transaction pattern
try {
await Database.beginTransaction();
// 1. Create pending record
const pendingRecord = await Database.run(
'INSERT INTO transactions (...) VALUES (...)',
[..., 'pending']
);
// 2. Execute Stellar transaction
const stellarResult = await stellarService.sendDonation(...);
// 3. Update record to confirmed
await Database.run(
'UPDATE transactions SET status = ?, stellarTxId = ? WHERE id = ?',
['confirmed', stellarResult.transactionId, pendingRecord.id]
);
await Database.commit();
} catch (error) {
await Database.rollback();
throw error;
}Severity: π΄ CRITICAL
Location: src/utils/encryption.js:11-21
Issue: Encryption key fallback to hardcoded value in development.
if (!key) {
if (process.env.NODE_ENV === 'production') {
throw new Error('ENCRYPTION_KEY must be set in production');
}
// β DANGEROUS FALLBACK
return Buffer.alloc(32, 'dev-secret-key-do-not-use-in-prod');
}Attack Vector:
- If NODE_ENV is not set correctly, weak key is used
- Key derivation from string is weak
- No key rotation mechanism
Recommendation:
- Always require ENCRYPTION_KEY, no fallback
- Use proper key derivation (PBKDF2, Argon2)
- Implement key rotation
- Store keys in secure vault (AWS KMS, HashiCorp Vault)
Severity: π΄ CRITICAL
Location: src/routes/donation.js:68-73
Issue: Amount validation is insufficient and can be bypassed.
if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
return res.status(400).json({ error: 'Amount must be a positive number' });
}Attack Vectors:
- Scientific notation bypass:
1e100 - Negative zero:
-0 - Infinity:
Infinity - Very large numbers causing overflow
- Very small numbers causing precision issues
Recommendation:
// Comprehensive amount validation
const amountNum = parseFloat(amount);
if (!Number.isFinite(amountNum) || amountNum <= 0) {
return res.status(400).json({ error: 'Invalid amount' });
}
if (amountNum > Number.MAX_SAFE_INTEGER) {
return res.status(400).json({ error: 'Amount too large' });
}
if (amountNum < 0.0000001) { // Stellar minimum
return res.status(400).json({ error: 'Amount too small' });
}
// Check decimal places (Stellar max 7)
const decimals = amount.toString().split('.')[1];
if (decimals && decimals.length > 7) {
return res.status(400).json({ error: 'Too many decimal places' });
}Severity: π΄ CRITICAL
Location: src/routes/donation.js:95-105
Issue: No timeout for Stellar transactions, can hang indefinitely.
Attack Vector:
- Resource exhaustion
- Hanging requests
- Poor user experience
Recommendation:
const TRANSACTION_TIMEOUT = 30000; // 30 seconds
const stellarResult = await Promise.race([
stellarService.sendDonation({...}),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Transaction timeout')), TRANSACTION_TIMEOUT)
)
]);Severity: π΄ CRITICAL
Location: src/routes/donation.js:175-188
Issue: Memo validation exists but sanitization may not prevent all injection attacks.
const sanitizedMemo = memo ? memoValidator.sanitize(memo) : '';Attack Vectors:
- XSS if memo is displayed in web interface
- SQL injection if memo is used in queries
- Command injection if memo is logged to shell
Recommendation:
// Enhanced memo sanitization
const sanitizeMemo = (memo) => {
if (!memo) return '';
// Remove all control characters
let sanitized = memo.replace(/[\x00-\x1F\x7F-\x9F]/g, '');
// HTML encode special characters
sanitized = sanitized
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
// Truncate to Stellar limit
return memoValidator.truncate(sanitized);
};Severity: π΄ CRITICAL
Location: All POST endpoints
Issue: No CSRF token validation on state-changing operations.
Attack Vector:
- Cross-site request forgery
- Unauthorized donations from victim's account
Recommendation:
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
// Include CSRF token in responses
router.get('/donations/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});Severity: π HIGH
Location: src/middleware/apiKeyMiddleware.js
Issue: API keys stored in plain text in environment variables.
Recommendation:
- Use JWT tokens with expiration
- Implement OAuth 2.0
- Add API key rotation
- Hash API keys in storage
Severity: π HIGH
Issue: No limits on request body size.
Recommendation:
app.use(express.json({ limit: '10kb' }));Severity: π HIGH
Location: src/routes/donation.js:127
Issue: Error messages expose internal details.
res.status(500).json({
success: false,
error: 'Failed to send donation',
message: error.message // β Exposes internal errors
});Recommendation:
// Production error handling
if (process.env.NODE_ENV === 'production') {
res.status(500).json({
success: false,
error: 'Transaction failed',
code: 'TRANSACTION_ERROR'
});
} else {
// Detailed errors only in development
res.status(500).json({
success: false,
error: 'Failed to send donation',
message: error.message,
stack: error.stack
});
}Severity: π HIGH
Location: src/routes/donation.js:348
Issue: Status update endpoint doesn't verify transaction ownership.
Recommendation:
- Add ownership check
- Require admin permission for status updates
- Log all status changes
Severity: π HIGH
Issue: Multiple simultaneous donations from same wallet can cause race conditions.
Recommendation:
- Implement distributed locks (Redis)
- Use database row-level locking
- Queue transactions per wallet
Severity: π HIGH
Issue: No audit trail for sensitive operations.
Recommendation:
const auditLog = {
timestamp: new Date().toISOString(),
action: 'DONATION_CREATED',
userId: req.user.id,
walletId: senderId,
amount: amount,
ipAddress: req.ip,
userAgent: req.headers['user-agent']
};
await AuditLog.create(auditLog);Severity: π HIGH
Location: src/routes/donation.js:213-228
Issue: Daily limit can be bypassed by using different donor names.
Recommendation:
- Track by wallet address, not donor name
- Implement IP-based rate limiting
- Add device fingerprinting
Severity: π HIGH
Issue: Large transactions execute immediately without confirmation.
Recommendation:
if (amount > 1000) { // Large transaction threshold
// Require 2FA or email confirmation
const confirmationToken = generateToken();
await sendConfirmationEmail(user.email, confirmationToken);
return res.status(202).json({
success: true,
message: 'Confirmation required',
confirmationToken
});
}Severity: π‘ MEDIUM
Issue: Donor and recipient fields not sanitized.
Recommendation:
- Validate Stellar address format
- Sanitize all string inputs
- Implement whitelist validation
Severity: π‘ MEDIUM
Issue: Pending transactions never expire.
Recommendation:
- Add expiry timestamp to transactions
- Implement cleanup job for expired transactions
Severity: π‘ MEDIUM
Location: src/utils/memoValidator.js
Issue: Only checks byte length and control characters.
Recommendation:
- Add profanity filter
- Check for malicious patterns
- Implement content moderation
Severity: π‘ MEDIUM
Issue: Only daily limit exists, no hourly/minute limits.
Recommendation:
- Add sliding window rate limiting
- Implement per-minute transaction limits
- Add burst protection
Severity: π‘ MEDIUM
Issue: If webhooks are added later, no signature verification planned.
Recommendation:
- Implement HMAC signature verification
- Add replay attack protection
- Use timestamp validation
Severity: π‘ MEDIUM
Location: src/utils/feeCalculator.js
Issue: Fee calculation can be manipulated.
Recommendation:
- Server-side fee calculation only
- Don't accept fee from client
- Validate fee percentage bounds
Severity: π‘ MEDIUM
Issue: Transactions don't store IP, user agent, or geolocation.
Recommendation:
- Add metadata fields to transaction table
- Store for fraud detection
- Implement anomaly detection
Severity: π‘ MEDIUM
Issue: Doesn't check if Stellar network is operational before transactions.
Recommendation:
const networkStatus = await stellarService.checkNetworkHealth();
if (!networkStatus.operational) {
return res.status(503).json({
error: 'Stellar network temporarily unavailable'
});
}Severity: π‘ MEDIUM
Issue: All transactions treated equally, no priority system.
Recommendation:
- Implement priority queue for large donations
- Add VIP user fast-track
- Queue management for high load
Severity: π‘ MEDIUM
Issue: Same amount to same recipient at same time not detected.
Recommendation:
- Implement fuzzy duplicate detection
- Add confirmation for suspicious patterns
- Machine learning for fraud detection
Severity: π‘ MEDIUM
Issue: Error messages don't always indicate what's wrong.
Recommendation:
- Provide specific field-level errors
- Include validation rules in error response
- Add error codes for client handling
Severity: π‘ MEDIUM
Issue: No way to cancel pending transactions.
Recommendation:
- Add cancellation endpoint
- Implement refund mechanism
- Add dispute resolution process
Severity: π‘ MEDIUM
Issue: Stellar network fees not accounted for in balance checks.
Recommendation:
const STELLAR_BASE_FEE = 0.00001; // 100 stroops
const totalRequired = parseFloat(amount) + STELLAR_BASE_FEE;
if (balance < totalRequired) {
return res.status(400).json({
error: 'Insufficient balance including network fee',
required: totalRequired,
available: balance
});
}Severity: π‘ MEDIUM
Issue: Each donation is individual transaction, inefficient for multiple donations.
Recommendation:
- Implement transaction batching
- Add bulk donation endpoint
- Optimize for gas fees
Severity: π‘ MEDIUM
Location: src/utils/memoValidator.js
Issue: Only supports MEMO_TEXT, not MEMO_ID, MEMO_HASH, or MEMO_RETURN.
Recommendation:
- Support all Stellar memo types
- Add memo type parameter
- Validate based on memo type
Severity: π’ LOW
Recommendation: Add analytics for fraud detection and business intelligence.
Severity: π’ LOW
Recommendation: Allow users to tag transactions for organization.
Severity: π’ LOW
Recommendation: Plan for future multi-asset support.
Severity: π’ LOW
Recommendation: Allow private notes separate from blockchain memo.
Severity: π’ LOW
Recommendation: Implement full-text search on transactions.
Severity: π’ LOW
Recommendation: Add CSV/PDF export for tax purposes.
Severity: π’ LOW
Recommendation: Allow scheduling donations for future dates.
Vector: Attacker discovers user IDs and drains wallets via /donations/send
Mitigation: Add authentication and ownership verification
Vector: Reuse idempotency keys to duplicate transactions
Mitigation: Implement idempotency key tracking
Vector: Send multiple simultaneous donations to overdraw wallet
Mitigation: Implement locking mechanism
Vector: Inject malicious content via memo field
Mitigation: Enhanced sanitization and validation
Vector: Use scientific notation or edge cases to bypass limits
Mitigation: Comprehensive amount validation
Vector: Flood donation endpoints to exhaust resources
Mitigation: Rate limiting and request throttling
Vector: Measure response times to infer wallet balances
Mitigation: Constant-time operations, add random delays
Vector: Enumerate valid user IDs and wallet addresses
Mitigation: Generic error messages, rate limiting
- β
Add authentication to
/donations/send - β Implement idempotency key checking
- β Add rate limiting to all endpoints
- β Implement balance checks before transactions
- β Add comprehensive amount validation
- β Implement transaction atomicity
- β Migrate to non-custodial model
- β Implement proper key management (HSM/KMS)
- β Add audit logging
- β Implement CSRF protection
- β Add request size limits
- β Improve error handling
- β Implement multi-signature wallets
- β Add fraud detection system
- β Implement transaction monitoring
- β Add compliance checks (AML/KYC)
- β Implement disaster recovery
- β Add security testing automation
- AML (Anti-Money Laundering): Implement transaction monitoring
- KYC (Know Your Customer): Add identity verification for large transactions
- CTF (Counter-Terrorism Financing): Screen against sanctions lists
- GDPR: Add data retention policies, right to deletion
- PCI DSS: If handling card data, ensure compliance
- SOC 2: Implement security controls for audit
- Travel Rule: For transactions > $1000, collect sender/receiver info
- Licensing: Check if money transmitter license required
- Tax Reporting: Implement 1099 reporting for US users
- Penetration testing by third party
- Automated security scanning (SAST/DAST)
- Dependency vulnerability scanning
- Fuzz testing on all inputs
- Stress test donation endpoints
- Test concurrent transaction handling
- Verify rate limiting effectiveness
- Test Stellar network failure scenarios
- Test database transaction rollbacks
- Test idempotency key handling
- Failed transactions > 5% in 5 minutes
- Unusual transaction patterns
- Multiple failed authentication attempts
- Encryption key access
- Large transactions (> $10,000)
- Transaction success rate
- Average transaction time
- API error rates
- Rate limit hits
- Wallet balance changes
- Detect and alert
- Isolate affected systems
- Investigate and contain
- Eradicate threat
- Recover systems
- Post-incident review
- Notify users within 72 hours
- Report to regulators as required
- Document incident thoroughly
- Implement preventive measures
The donation flow has significant security vulnerabilities that require immediate attention. The most critical issues are:
- Missing authentication on /donations/send - Allows unauthorized wallet access
- Custodial key storage - Single point of failure for all funds
- No idempotency checking - Allows duplicate transactions
- Missing rate limiting - Vulnerable to abuse
- Insufficient input validation - Multiple injection vectors
Recommended Priority:
- Week 1: Fix all CRITICAL issues (1.1-1.12)
- Month 1: Address HIGH priority issues (2.1-2.8)
- Quarter 1: Resolve MEDIUM priority issues (3.1-3.15)
- Ongoing: Monitor and improve LOW priority items
Estimated Effort: 4-6 weeks for critical fixes, 3-4 months for complete remediation.
Report Status: DRAFT
Next Review: After critical fixes implemented
Approval Required: Security Team, Engineering Lead, Product Owner