Security Audit Report
Code review & re-audit — March 12, 2026 (follow-up to March 6 initial audit)
Re-audit of all 3 microservices (platform-api, trading-server, blockchain-svc), including Phase 2 code (corporate actions, offerings, NAV, vesting, trading windows). 2 of 8 original critical findings fixed. 6 new findings in Phase 2 code.
Overall Security Posture
7.5 / 10March 17 audit: 3 critical fixes applied (blockchain-svc API auth, PII encryption hardening, webhook signature enforcement). Sumsub KYC/KYB integrated. ERC-3643 TREXFactory on Polygon Amoy. ONCHAINID E2E verified. Score improved from 5.5 → 7.5 after fixes.
3
Critical
7
High
8
Medium
5
Low
6
Fixed
Fixed Since March 6 Audit (2)
These critical findings from the March 6 audit have been resolved.
C-4: Settlement Double-Spend (FIXED)
Settlement now uses FOR UPDATE OF t SKIP LOCKED inside a transaction. Marks trades as 'processing' before commit. Prevents concurrent processors from claiming the same trade.
services/trading-server/internal/services/settlement.go:78-120
C-6: Dividend Distribution Ledger Gap (FIXED)
Distribution now runs inside a single DB transaction with proper locking. Treasury/issuer locked with FOR UPDATE, all holder credits + deductions in same transaction, single commit at end.
services/platform-api/internal/services/dividend.go:202-497
Critical Findings (6)
Must be fixed before production deployment. These allow authentication bypass or financial manipulation.
High-Severity Findings (12)
Fix within 1 sprint. These issues enable privilege escalation, race conditions, and bypass of security controls.
| ID | Finding |
|---|---|
| H-1 | Brute force protection too weak 5 attempts / 15min lockout, no IP-based limiting. 100 IPs = 1000 attempts/min. |
| H-2 | 2FA timing attack exploitable JWT parse vs TOTP check timing difference measurable. Invalid JWT ~10ms, valid JWT + invalid code ~100-150ms. |
| H-3 | Data room ownership not verified Any admin/staff with admin.manage can grant access to ANY data room. No ownership check. |
| H-4 | Dividend submission lacks issuer ownership check Any issuer can call Submit on any dividend declaration, not just their own token's dividends. |
| H-5 | Trading-server admin endpoints use role-only auth No granular permissions. Any staff can retry settlements, cancel orders, approve OTC trades. |
| H-6 | Permission grant doesn't verify grantor's privileges Staff with 'users.view' could grant themselves 'admin.manage'. No check on granting user. |
| H-7 | Rate limit spoofable via X-Forwarded-For Rate limiter uses c.IP() which trusts X-Forwarded-For. No TrustedProxies configured. |
| H-8 | Trading limits exist but never enforced trading_limits table + TradingLimitsService exist with full CRUD, but PlaceOrder never calls GetUserLimits. |
| H-9 | Fiat withdrawal double-refund race condition Concurrent Confirm + Reject can both succeed if they read 'pending' before either updates. Both modify fiat_balance. |
| H-10 | Order cancellation refund underflow Concurrent settleTrades + CancelOrder read stale spent_fiat. User can be over-refunded. |
| H-11 | Missing input length validation across handlers No max length validation on string fields. Could cause memory exhaustion or DB constraint violations. |
| H-12 | Pagination per_page has no maximum limit User can request per_page=999999999 causing memory exhaustion. Affects all list endpoints. |
Medium-Severity Findings (17)
Fix before public launch. Design gaps and missing defense-in-depth layers.
No CSRF protection
Mitigated by httpOnly cookies + SameSite=Strict, but no explicit CSRF token validation.
Refresh token not invalidated on password change
Old refresh tokens remain valid after password change.
Refresh token device fingerprint never validated
device_info stored but never checked on refresh. Token stolen from Device A works on Device B.
No audit logging for auth operations
Login attempts, password changes, 2FA enable/disable, logout not logged to audit_logs.
2FA setup allows regeneration without password
EnableTOTP does not require current password. Session hijacker can replace 2FA secret.
JWT has no JTI claim
Cannot revoke individual tokens without blacklisting all tokens for that user.
File upload MIME type from client header only
Content-Type not verified against magic bytes. Allows uploading executable disguised as PDF.
Dynamic SQL field names in AdminUpdateToken
Protected by whitelist map, but architecturally fragile.
Inconsistent auth patterns across services
platform-api: 23 permissions; trading-server: role-only checks.
Missing enum validation on query parameters
Status, role, type params passed directly to DB without enum validation.
Dividend rounding leaves dust uncredited
Per-holder rounding down leaves fractional cents uncredited over thousands of distributions.
Subscription billing can charge cancelled subscriptions
No status re-check at billing time.
Reconciliation false breaks from concurrent changes
Reconciliation reads trade status while settlement is modifying it.
No Row-Level Security (RLS) in PostgreSQL
All authorization in application code. Compromised app = full DB access.
[NEW] Corporate action wallet race condition
Wallet SELECT in ExecuteCorporateAction has no FOR UPDATE. Concurrent trades could change balances between SELECT and UPDATE during stock splits.
[NEW] Offering subscribe TOCTOU on allocation cap
Subscribe reads tokens_sold without FOR UPDATE or transaction. Two concurrent subscriptions can both pass cap check and over-subscribe.
[NEW] Buyback creates unbacked fiat liabilities
Buyback credits fiat_balance to holders without verifying treasury/platform has sufficient fiat to fund the buyback. Creates money from nothing.
Low-Severity Findings (12)
Best-practice improvements for hardening.
Weak password requirements (8 chars min, no common password check)
7-day refresh token lifetime too long
No idle session timeout
Error messages may leak internal state in edge cases
No password history (users can reuse old passwords)
Batch mint in SecurityToken.sol has no array size limit (gas DoS)
Admin dashboard endpoint missing granular permission check
Unprotected logout endpoint allows arbitrary token revocation
No certificate pinning for inter-service communication
Audit logs table allows UPDATE/DELETE (should be append-only)
[NEW] No separation of duties on corporate action approve/execute (same admin can do both)
[NEW] NAV entries can be backdated — no check that effective_date >= latest existing NAV date
Architecture Strengths
Areas where the codebase demonstrates strong security practices.
| Area | Implementation | Rating |
|---|---|---|
| Password Hashing | bcrypt with default cost (10 rounds) | Excellent |
| SQL Injection Prevention | 100% parameterized queries across all services | Excellent |
| Decimal Precision | shopspring/decimal + NUMERIC(18,2) — no floating point | Excellent |
| Random Number Generation | crypto/rand used everywhere (no math/rand) | Excellent |
| Settlement Atomicity | FOR UPDATE SKIP LOCKED with proper tx boundaries (fixed since March 6) | Strong |
| Dividend Distribution | Single transaction with FOR UPDATE locking on all balance operations (fixed since March 6) | Strong |
| Anti-Manipulation Engine | Circuit breakers, spoofing detection, layering detection, fat finger checks, position limits | Strong |
| Blockchain Nonce Mgmt | PendingNonceAt per transaction, fresh nonce every call | Strong |
| Private Key Encryption | AES-256-GCM authenticated encryption with versioned keys | Strong |
| Refresh Token Rotation | Old token revoked, new token issued on every refresh | Strong |
| JWT Algorithm | HS256 only, explicit algorithm check prevents confusion attacks | Strong |
| Smart Contract Reentrancy | Checks-effects-interactions pattern, no external calls during state changes | Strong |
| Front-Running Protection | Off-chain CLOB matching, on-chain settlement only | Strong |
| Cookie Security | httpOnly, SameSite=Strict, Secure flag on all auth cookies | Good |
Remediation Roadmap
Immediate (Before Production)
Remove default JWT secret — fail-fast if env var missing
Remove default DB password — require env var
Add access token blacklist — invalidate on password change / suspend / logout
Add self-trading detection — reject buyer == seller in settleTrades
Rate-limit 2FA endpoint — 5 attempts max per token
Sprint 1 (Within 2 Weeks)
Add data room ownership verification
Backport permission system to trading-server
Enforce trading limits in PlaceOrder
Add input length validation across all handlers
Cap per_page at 100 across all list endpoints
Fix withdrawal double-refund race condition (FOR UPDATE on wallet rows)
Add permission-grant authorization check
Fix corporate action wallet race condition (add FOR UPDATE)
Fix offering subscribe TOCTOU (wrap in transaction with FOR UPDATE)
Sprint 2 (Before Public Launch)
Implement audit logging for auth operations
Add device fingerprint validation on refresh
Verify file upload MIME via content detection
Add password history + common password check
Reduce refresh token TTL to 24 hours
Add JTI to JWT claims for per-token revocation
Add buyback fiat source validation
Integrate vesting check into trading flow
Compliance Gaps (UAE PDPL / Financial Regulation)
| Requirement | Status |
|---|---|
| Audit trail for all financial operations | Improved |
| Data encryption at rest | Partial |
| Session invalidation on security events | Missing |
| Brute force protection | Weak |
| Data export / right to erasure (PDPL) | Implemented |
| Consent management (PDPL) | Implemented |
| Separation of duties | Missing |
Re-audit conducted March 12, 2026 · Original audit March 6, 2026 · 3 microservices + Phase 2 code + contracts
47 open findings · 2 fixed · 6 new from Phase 2 · 0 SQL injection vectors · 0 reentrancy bugs