Date: May 28, 2026
Audit Scope: /backend/src
Assessment Level: Active Development → Production Ready
The backend codebase demonstrates strong foundational practices with good architecture patterns and security awareness. However, there are critical gaps in production readiness that must be resolved before handling financial transactions at scale:
| Area | Status | Risk Level | Comments |
|---|---|---|---|
| Error Handling | ✅ Good | Low | Comprehensive error types, proper propagation |
| Input Validation | Critical | Validation logic broken, inconsistent schema usage | |
| Database Transactions | ❌ Missing | Critical | No transaction wrapping, no rollback safety |
| Logging | Medium | Mixed console/winston, sensitive data exposure | |
| Rate Limiting | ✅ Good | Low | Layered approach, configurable per endpoint |
| Access Control | ✅ Good | Low | Stellar address verification, project ownership checks |
| Configuration | ✅ Good | Low | Zod validation, environment safety |
| Response Validation | Medium | Middleware in place but incomplete coverage | |
| Migration Safety | ✅ Good | Low | Reversible migrations with proper structure |
| Test Coverage | Medium | Core happy paths covered, gaps in error scenarios |
- Structured error types with
ErrorTypeenum (CONTRACT, AUTH, VALIDATION, ACCOUNT_STATE, RPC, INTERNAL) - Detailed error codes mapping contract errors to user-friendly messages with remediation hints
- Centralized error handler in middleware/error.ts
- Error propagation pattern: All route handlers catch and forward via
next(error) - Error logging in handler includes request ID, error type, code, message, details
File: middleware/validate.ts
Severity: CRITICAL
Issue: Response body is incomplete/broken - has syntax errors
// BROKEN CODE:
res.status().json({
error: , // ← missing value
message: , // ← missing value
requestId,
details: // ← missing value
});Impact: Any route using validateRequest middleware will crash on validation error
Fix: Complete the response object:
res.status(400).json({
error: "validation_error",
message: "Invalid request payload.",
requestId,
details: buildValidationDetails(error)
});Files:
- middleware/error.ts
- middleware/validateResponse.ts
- services/stellar.ts (console.warn)
Severity: MEDIUM
Issue: Error handler uses console.error() instead of logger.error()
- Not captured by log rotation
- Not sent to error.log file
- Inconsistent with rest of codebase
Impact: Production errors won't appear in centralized logs
Count: 3 console.error calls
- Zod schemas for all major request payloads
- Stellar address validation with regex and Address.fromString() checks
- Custom validators for business logic (basisPoints sum to 10,000, no duplicate collaborators)
- Schema composition for reusable primitives
File: middleware/validate.ts
Severity: CRITICAL
Issue: The validateRequest middleware has a syntax error and is not being used consistently
// validateRequest is defined but:
// 1. Not imported in any routes
// 2. Response handler broken (see Gap 1.1)
// 3. Routes validate manually with .safeParse() insteadCurrent pattern (routes do it directly):
// From routes/users.ts
const parsed = userRegistrationSchema.safeParse(req.body);
if (!parsed.success) {
throw new AppError(..., parsed.error.flatten());
}Impact: Validation is scattered, not DRY, harder to audit
Recommendation: Fix and use middleware consistently
File: routes/transactions.ts
// GET /transactions/:txHash lacks path parameter validation
const { txHash } = req.params;
if (!txHash || txHash.length === 0) {
throw new AppError(..., "Transaction hash is required.");
}
// ← Basic string check only, no format validationIssue: Should validate Stellar transaction hash format (64 hex chars or XDR format)
Risk: Bad input can cause RPC failures or crash downstream parsing
Multiple files define stellarAddressSchema:
- schemas/user.schemas.ts - regex only
/^G[A-Z2-7]{55}$/ - schemas/splits.schemas.ts - uses
Address.fromString() - routes/splits.ts - duplicate definition
Issue: Duplicate code, inconsistent validation depth
Impact: Some endpoints accept malformed addresses, others reject valid ones
File: routes/users.ts
Issue: Index.ts registers auth limiter for "/users/login" but route doesn't exist
// index.ts line 73
app.use("/users/login", authLimiter);
// but no route handler for POST /users/loginRisk: Orphaned middleware, unauthenticated socket connection possible
Status: NOT IMPLEMENTED
Issue: Financial operations must be wrapped in database transactions for consistency. Current code doesn't use transactions at all.
Examples:
users.ts - User registration:
// Lines 44-63
const newUser = userRepository.create({...});
const savedUser = await userRepository.save(newUser); // ← NO TRANSACTIONMissing concern: If an error occurs between queries, data becomes inconsistent.
What's missing:
- No use of
QueryRunnerfor transaction management - No rollback pattern
- No isolation level specification
- No deadlock retry logic
High-Risk Operations That Need Transactions:
- User registration - if wallet address unique constraint violated mid-save
- Transaction recording - if recording fails after blockchain confirmation
- Payout history - if file write fails during cache update
Code Pattern Needed:
const queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction();
try {
// Do work
await queryRunner.commitTransaction();
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}Severity: CRITICAL - Data corruption risk in production
- Winston logger configured with file rotation (error.log, combined.log)
- Structured logging with metadata objects:
logger.info("event", { userId, walletAddress, requestId }) - Request ID tracking via middleware/request-id.ts - every log includes requestId
Files:
- services/stellar.ts:
console.warn()for RPC retries - services/PayoutHistoryService.ts:
console.log()for backfill - middleware/error.ts:
console.error()for error logging - config/env.ts:
console.log()for diagnostics
Impact:
- Not captured by file rotation
- CI logs mixed with stderr
- Harder to parse/aggregate logs
Count: 13 console calls that should be logger calls
File: middleware/project-access.ts
logger.warn("Unauthorized project access attempt blocked", {
requester, // ← Stellar address - OK for audit trails
projectId, // ← OK
requestId, // ← OK
ip: req.ip, // ← IP address - consider privacy implications
});Concern: IP addresses logged unredacted could violate GDPR/privacy laws
Risk: Low for blockchain context but should be hashed in production
File: middleware/error.ts
// Only logs error details, not request context
console.error({
requestId,
type: err.type,
code: err.code,
message: err.message,
details: err.details
});
// Missing: err.stack, original request path/methodImpact: Hard to debug production issues without full context
File: services/stellar.ts
console.debug(`[cache] MISS (expired) key=${key}`);
console.debug(`[cache] HIT key=${key}`);
// Should use logger.debug() with consistent formatImpact: Cache performance debugging not integrated into Winston logs
Strengths:
-
Layered strategy with 5 tiers:
globalLimiter: 500 req/15min (safety net for all routes)readLimiter: 100 req/15min (GET endpoints)writeLimiter: 30 req/15min (POST/PUT/DELETE)adminLimiter: 20 req/15min (admin endpoints)authLimiter: 10 req/5min (registration/login - strict burst protection)
-
Environment-configurable: All limits can be tuned via
process.env:RATE_LIMIT_WINDOW_MSRATE_LIMIT_GLOBAL_MAXRATE_LIMIT_WRITE_MAXRATE_LIMIT_ADMIN_MAXRATE_LIMIT_AUTH_WINDOW_MS
-
Proper headers: Uses standard rate-limit headers (Retry-After)
-
Applied consistently: Every route has rate limiting
-
Skip logic: Health checks bypass global limiter
File: middleware/rate-limit.ts
Note: Health endpoint properly skipped to avoid false alerting from monitors
Strengths:
-
Stellar address verification via middleware/project-access.ts
- Validates
X-Stellar-Addressheader format - Ensures only valid Stellar public keys accepted
- Requires signature proof (enforced at wallet level with Freighter)
- Validates
-
Project ownership checks -
requireProjectAccess()middleware:- Verifies requester is either project owner OR collaborator
- Prevents unauthorized enumeration of private project data
- Logs unauthorized attempts with context
-
Role-based access in User entity:
@Column({ type: "varchar", length: 32, default: "user" }) role!: string;
(Though role enforcement not fully implemented yet)
File: middleware/project-access.ts
Issue: User entity has role field but no middleware enforces it
- No admin role checks on
/splits/adminendpoint - No permission matrix for role → action
Recommendation: Implement requireRole() middleware
Issue: No JWT or session tokens for repeated auth
- Users must provide X-Stellar-Address header on every request
- No logout mechanism
- No token expiration
Note: This aligns with wallet-first design but limits features like "stay logged in"
Strengths:
-
Zod schema validation for all environment variables
-
Type-safe config with
BackendEnvinterface -
Detailed error messages if vars are missing:
[env] Server cannot start - fix the following environment variable issues: x DATABASE_URL: DATABASE_URL must be a valid PostgreSQL connection string -
Caching to avoid repeated validation via
getEnv() -
Production-specific handling:
- Automatic SSL for non-localhost databases
- CSP headers relaxed only for /docs route
Files:
All sensitive config (DATABASE_URL, RPC URLs, contract IDs) properly required and validated.
Strengths:
- Middleware exists: middleware/validateResponse.ts
- Schema validation: Responses validated before sending
- Non-breaking: In production, validates but still sends response (soft fail)
- Development mode: Sends validation error details for debugging
Code:
export function withResponseValidation<T>(
schema: ZodSchema<T>,
handler: RouteHandler,
): RouteHandler {
// Validates res.json() output against schema
// In production: logs drift but sends response anyway
// In development: returns validation error
}Usage: Only found in /api/openapi.json handler
- User registration responses: Not validated
- Transaction history responses: Not validated
- Split project list responses: Not validated
Files affected:
- routes/users.ts - creates responses manually
- routes/transactions.ts - no validation
- routes/splits.ts - no validation
Impact: Response shape changes won't be caught until frontend breaks
Files:
- schemas/user.schemas.ts - only registration schema
- Missing: user list response, user profile response
Recommendation: Define schemas for every response type and apply middleware consistently:
splitsRouter.post("/",
withResponseValidation(ProjectListResponseSchema, async (req, res, next) => {
// handler
})
);Strengths:
- Reversible migrations with
up()anddown()methods - Proper structure - creates extensions, tables, indexes, enums
- Unique constraints to prevent duplicates (walletAddress, txHash)
- Composite indexes for query optimization:
- txHash (unique, for lookups)
- roundId (for batch operations)
- recipient (for user queries)
- timestamp (for range queries)
File: migrations/1760000000000-InitialUserAndTransactionRecord.ts
down() method properly drops:
DROP INDEX IDX_transactions_timestamp;
DROP INDEX IDX_transactions_recipient;
DROP INDEX IDX_transactions_round_id;
DROP INDEX IDX_transactions_tx_hash;
DROP TABLE transactions;
DROP TYPE transactions_status_enum;
DROP TABLE users;Good practice but could be safer:
await queryRunner.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);Better: Fail explicitly if uuid-ossp not available (PostgreSQL may be too old)
File: migrations/1760000000000-InitialUserAndTransactionRecord.ts
CREATE TYPE "public"."transactions_status_enum"
AS ENUM('pending', 'completed', 'failed')Issue: Hardcoded status values; if entity changes, migration doesn't auto-update
Note: This is a TypeORM limitation, not a code bug
Issue: No migrations for future schema changes (adding columns, renaming, etc.)
Status: Not applicable yet - first migration only
Test Files: 8 files in tests/
- ✅
e2e-happy-path.test.ts- Full core flow - ✅
routes.test.ts- Route integration tests - ✅
users.test.ts- User registration happy path - ✅
history.test.ts- Split history filtering - ✅
rpc-retries.test.ts- Retry logic - ✅
transactions.test.ts- Transaction queries - ✅
auth-middleware.test.ts- Stellar auth - ✅
contract-interface-artifact.test.ts- Contract validation
- Happy path coverage: Core flows (create split → deposit → distribute) tested
- Error simulation: Mock Stellar RPC to test failure scenarios
- Retry logic: Explicit tests for exponential backoff
- Auth verification: Stellar address validation tested
- Transaction filtering: History query parameters tested
Missing: Unit tests for middleware/error.ts
// No test coverage for:
// - AppError formatting
// - Non-AppError error handling
// - RpcError handling
// - Fallback error responseImpact: Error path bugs won't be caught
Missing: Tests for:
- User registration with duplicate wallet address (concurrency)
- Transaction recording failures and rollback
- Database connection loss
Critical for financial app reliability
Missing: Tests for middleware/validate.ts (it's broken anyway)
Impact: Can't verify validation error responses without fixing the middleware
Missing:
- Tests that verify rate limit headers are set correctly
- Tests that verify different endpoints have different limits
- Tests that verify IP-based rate limiting works
Missing: Tests for middleware/validateResponse.ts
Missing:
- Tests that verify non-owner can't access private project data
- Tests that verify collaborators can access their projects
- Tests that verify unauthorized attempts are logged
Example: No test for requireProjectAccess() returning 403 Unauthorized
Missing: Tests for config/env.ts
// No tests for:
// - validateEnv() with missing required vars
// - validateEnv() with invalid port
// - getEnvDiagnostics() with various issues| Test Category | Covered | Missing | Risk |
|---|---|---|---|
| Happy path | ✅ Core flow | - | Low |
| Error handling | Comprehensive error tests | High | |
| Database | ❌ Not tested | Transactions, concurrency, rollback | Critical |
| Validation | ❌ Broken middleware | All validation tests | Critical |
| Rate limiting | ❌ No tests | Limit verification, headers | Medium |
| Access control | ❌ No tests | Authorization checks | High |
| Configuration | ❌ No tests | Env validation, diagnostics | Medium |
| Response validation | ❌ No tests | Response shape contracts | Medium |
-
Fix validate.ts middleware middleware/validate.ts
- Broken response body syntax
- Estimated effort: 15 min
-
Implement database transactions
- Wrap user registration, transaction recording in transactions
- Add rollback error handling
- Estimated effort: 3-4 hours
-
Add database transaction tests
- Concurrency scenarios, rollback verification
- Estimated effort: 2-3 hours
-
Implement response validation middleware usage
- Apply to all routes with
withResponseValidation() - Create comprehensive response schemas
- Estimated effort: 2-3 hours
- Apply to all routes with
-
Replace console logging with logger
- Migrate 13 console calls to winston logger
- Estimated effort: 1 hour
-
Complete error handling tests
- Test all error paths, edge cases
- Estimated effort: 2 hours
-
Implement access control tests
- Verify authorization checks work
- Test logged attempts
- Estimated effort: 1-2 hours
-
Add validation tests
- Test all Zod schemas, edge cases
- Test malformed input rejection
- Estimated effort: 2 hours
-
Implement admin role enforcement
- Create
requireRole()middleware - Protect
/splits/adminendpoint - Estimated effort: 1 hour
- Create
-
Document deployment/config requirements
- .env.example file
- Migration runbook
- Rollback procedures
- Estimated effort: 1-2 hours
- Fix validate.ts middleware (15 min)
- Implement database transactions (3-4 hours)
- Apply response validation middleware (2-3 hours)
- Replace console with logger (1 hour)
- Add critical test coverage (4-5 hours)
- Complete error handling tests
- Implement access control tests
- Add validation tests
- Admin role enforcement
- Documentation
- Performance testing under load
- Security audit of RPC integration
- Database backup/recovery procedures
- Monitoring and alerting setup
- Load testing rate limiters
Security:
- All console logging replaced with logger
- No sensitive data (private keys, secrets) in logs
- Rate limiting enforced on all endpoints
- Access control verified with tests
- Input validation complete and tested
- Response shapes validated
Reliability:
- Database transactions wrapping critical operations
- Error handling comprehensive with retry logic
- Graceful shutdown implemented ✓
- Health checks in place ✓
- Migrations reversible ✓
Operations:
- All environment variables documented
- Configuration validated at startup ✓
- Logs rotated and archived
- Database backups scheduled
- Monitoring configured
- Runbooks for common issues
Quality:
- Critical path tests passing
- Error scenarios tested
- Rate limiting tested
- Access control tested
- Configuration tested
- Code coverage > 80% for critical paths
Audit Performed On:
- index.ts - App setup
- config/env.ts - Configuration
- lib/errors.ts - Error types
- middleware/ - All middleware
- routes/ - All route handlers
- services/ - Business logic
- schemas/ - Validation schemas
- entities/ - Data models
- migrations/ - Schema migrations
- tests/ - Test suites
Audit Completed: May 28, 2026
Recommend Review With: Tech Lead, DevOps, QA before production cutover