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 / 10

March 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.

IDFinding
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.

M-1

No CSRF protection

Mitigated by httpOnly cookies + SameSite=Strict, but no explicit CSRF token validation.

M-2

Refresh token not invalidated on password change

Old refresh tokens remain valid after password change.

M-3

Refresh token device fingerprint never validated

device_info stored but never checked on refresh. Token stolen from Device A works on Device B.

M-4

No audit logging for auth operations

Login attempts, password changes, 2FA enable/disable, logout not logged to audit_logs.

M-5

2FA setup allows regeneration without password

EnableTOTP does not require current password. Session hijacker can replace 2FA secret.

M-6

JWT has no JTI claim

Cannot revoke individual tokens without blacklisting all tokens for that user.

M-7

File upload MIME type from client header only

Content-Type not verified against magic bytes. Allows uploading executable disguised as PDF.

M-8

Dynamic SQL field names in AdminUpdateToken

Protected by whitelist map, but architecturally fragile.

M-9

Inconsistent auth patterns across services

platform-api: 23 permissions; trading-server: role-only checks.

M-10

Missing enum validation on query parameters

Status, role, type params passed directly to DB without enum validation.

M-11

Dividend rounding leaves dust uncredited

Per-holder rounding down leaves fractional cents uncredited over thousands of distributions.

M-12

Subscription billing can charge cancelled subscriptions

No status re-check at billing time.

M-13

Reconciliation false breaks from concurrent changes

Reconciliation reads trade status while settlement is modifying it.

M-14

No Row-Level Security (RLS) in PostgreSQL

All authorization in application code. Compromised app = full DB access.

M-15

[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.

M-16

[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.

M-17

[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.

L-1

Weak password requirements (8 chars min, no common password check)

L-2

7-day refresh token lifetime too long

L-3

No idle session timeout

L-4

Error messages may leak internal state in edge cases

L-5

No password history (users can reuse old passwords)

L-6

Batch mint in SecurityToken.sol has no array size limit (gas DoS)

L-7

Admin dashboard endpoint missing granular permission check

L-8

Unprotected logout endpoint allows arbitrary token revocation

L-9

No certificate pinning for inter-service communication

L-10

Audit logs table allows UPDATE/DELETE (should be append-only)

L-11

[NEW] No separation of duties on corporate action approve/execute (same admin can do both)

L-12

[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.

AreaImplementationRating
Password Hashingbcrypt with default cost (10 rounds)Excellent
SQL Injection Prevention100% parameterized queries across all servicesExcellent
Decimal Precisionshopspring/decimal + NUMERIC(18,2) — no floating pointExcellent
Random Number Generationcrypto/rand used everywhere (no math/rand)Excellent
Settlement AtomicityFOR UPDATE SKIP LOCKED with proper tx boundaries (fixed since March 6)Strong
Dividend DistributionSingle transaction with FOR UPDATE locking on all balance operations (fixed since March 6)Strong
Anti-Manipulation EngineCircuit breakers, spoofing detection, layering detection, fat finger checks, position limitsStrong
Blockchain Nonce MgmtPendingNonceAt per transaction, fresh nonce every callStrong
Private Key EncryptionAES-256-GCM authenticated encryption with versioned keysStrong
Refresh Token RotationOld token revoked, new token issued on every refreshStrong
JWT AlgorithmHS256 only, explicit algorithm check prevents confusion attacksStrong
Smart Contract ReentrancyChecks-effects-interactions pattern, no external calls during state changesStrong
Front-Running ProtectionOff-chain CLOB matching, on-chain settlement onlyStrong
Cookie SecurityhttpOnly, SameSite=Strict, Secure flag on all auth cookiesGood

Remediation Roadmap

Immediate (Before Production)

1.

Remove default JWT secret — fail-fast if env var missing

2.

Remove default DB password — require env var

3.

Add access token blacklist — invalidate on password change / suspend / logout

4.

Add self-trading detection — reject buyer == seller in settleTrades

5.

Rate-limit 2FA endpoint — 5 attempts max per token

Sprint 1 (Within 2 Weeks)

1.

Add data room ownership verification

2.

Backport permission system to trading-server

3.

Enforce trading limits in PlaceOrder

4.

Add input length validation across all handlers

5.

Cap per_page at 100 across all list endpoints

6.

Fix withdrawal double-refund race condition (FOR UPDATE on wallet rows)

7.

Add permission-grant authorization check

8.

Fix corporate action wallet race condition (add FOR UPDATE)

9.

Fix offering subscribe TOCTOU (wrap in transaction with FOR UPDATE)

Sprint 2 (Before Public Launch)

1.

Implement audit logging for auth operations

2.

Add device fingerprint validation on refresh

3.

Verify file upload MIME via content detection

4.

Add password history + common password check

5.

Reduce refresh token TTL to 24 hours

6.

Add JTI to JWT claims for per-token revocation

7.

Add buyback fiat source validation

8.

Integrate vesting check into trading flow

Compliance Gaps (UAE PDPL / Financial Regulation)

RequirementStatus
Audit trail for all financial operationsImproved
Data encryption at restPartial
Session invalidation on security eventsMissing
Brute force protectionWeak
Data export / right to erasure (PDPL)Implemented
Consent management (PDPL)Implemented
Separation of dutiesMissing

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