Institutional dossier · prepared for Investec Mauritius

The technical, security and compliance posture of FinVeil — in detail.

The pages at /trust, /security and /compliance are written for prospective customers. This page is written for institutional evaluators. Every claim here cites the file in our repository where the control lives so you can audit, not infer. Where a control is not yet in place we say so explicitly — the "What is not yet in place" section below is the most important one on this page.

1. Technology stack & the rationale behind each choice

Every layer below is a deliberate choice with a security or operational trade-off attached. The evidence column points to the file you can read for yourself.

LayerChoiceWhyEvidence in repo
Backend frameworkSpring Boot 4.0.0 on Java 25, built with Maven 3.9+Mature enterprise tooling, hardened security primitives, deep SA hiring pool. JaCoCo + Flyway + Actuator give us coverage gates, replayable migrations, and ops introspection out of the box.backend/pom.xml (spring-boot-starter-parent 4.0.0, java.version 25)
FrontendNext.js 15.1 + React 19 + TypeScript 5.6 (strict) + Tailwind 3.4SSR / RSC give us SEO on marketing pages; route handlers act as a BFF so JWTs stay in httpOnly cookies and never reach the browser JS heap. TypeScript strict mode catches contract drift between API and UI at compile time.frontend/package.json
DatabasePostgreSQL 16, single-region managed (Railway, eu-west Frankfurt)ACID, native enums for risk levels and roles, JSONB for contributing-factor maps, partial indexes for tenant-scoped queries. Same engine we develop against locally — no cross-environment drift.backend/src/main/resources/application-prod.yml
AuthenticationSpring Security + JWT (60-min access, 7-day refresh), BCrypt cost 12Stateless bearer auth across SPA, mobile SDKs, server-to-server, and webhooks. BCrypt cost 12 follows OWASP 2024 fintech guidance. CSRF is explicitly disabled and justified inline (no session cookie surface to ride).backend/src/main/java/io/finveil/config/SecurityConfig.java:61 (BCRYPT_COST=12); application.yml:151 (access-token-expiration 3 600 000 ms)
PII encryption at restAES-256-GCM per-field via JPA AttributeConverter (random 96-bit IV, 128-bit tag)Authenticated encryption with per-field IVs prevents both confidentiality and tamper-tag bypass. The lazy-init pattern keeps the Spring context bootable in test slices that never touch encrypted columns.backend/src/main/java/io/finveil/common/EncryptedStringConverter.java:41 (Converter, CIPHER_ALGO "AES/GCM/NoPadding")
Searchable PII (e.g. user.email)HMAC-SHA256 blind index in a sibling column (email_hash)Random-IV AES makes the ciphertext non-deterministic so a plain unique index is impossible. The HMAC blind index gives O(1) login lookups without leaking the plaintext to anyone holding only the DB dump.backend/src/main/java/io/finveil/auth/User.java:33 (email encrypted, email_hash HMAC); UserDetailsServiceImpl.hmacEmail()
Webhooks (Paystack/Yoco/PayFast/Ozow/Stitch/Rapyd/BulkSMS/SMSPortal/WhatsApp)HMAC-SHA256/SHA512 constant-time verification, signature IS the auth boundaryInbound paths live under /api/webhooks/** and are permitAll at the filter chain. The per-controller signature check uses MessageDigest.isEqual to defend against timing oracles.backend/src/main/java/io/finveil/webhook/HmacSha256Verifier.java:32; SecurityConfig.java:175 ("/api/webhooks/**")
Hosting / edgeRailway (Frankfurt) for API + Postgres; Vercel for marketing; TLS 1.3 everywhereManaged Postgres with point-in-time recovery, automatic Let's Encrypt cert rotation, container-level isolation. Marketing is fully static-rendered so an outage on the API never blocks the brand surface.backend/src/main/resources/application-prod.yml
Schema migrationsFlyway, versioned + checksummed, 75 migrations on HEADReplayable, environment-agnostic, fails fast on checksum drift so a hand-edited migration cannot brick Railway boot.backend/src/main/resources/db/migration/V*.sql
Async / guaranteed deliveryTransactional outbox + poller with exponential backoff (5s → 30s → 5m → 30m → 2h, then DLQ)Outbox row commits in the same transaction as the business write, so the platform cannot lose an event between commit and dispatch. Dead-lettered operations are surfaced in /admin/operations for human review.backend/src/main/java/io/finveil/outbox/OutboxService.java:36 (BACKOFF schedule)
Audit logAppend-only audit_log table, write on every mutating controller actionCaptures user id, employer id, action, entity, IP, timestamp, JSONB details. Retained ≥12 months. AuditLogService writes in REQUIRES_NEW so an audit failure never silently swallows a business commit.backend/src/main/java/io/finveil/audit/AuditLogService.java (Propagation.REQUIRES_NEW)
Cryptographic receiptsPer-transaction token with optional Stellar anchoring for independent verifiabilityA counterparty holding only the token id can verify a payment receipt against a public endpoint without any FinVeil credentials. Anchoring is opt-in and toggled per environment.backend/src/main/java/io/finveil/tokenization/ (TokenService, StellarAnchorService, ReceiptVerificationController)

2. Security architecture — request lifecycle

Default-deny at the Spring filter chain. CSRF rationale, public-endpoint surface and the order of ApiKeyAuthenticationFilter vs JwtAuthenticationFilter are all documented inline in SecurityConfig.java so a reviewer can audit the surface at a glance.

1. Browser / mobile / partner API caller

Next.js SPA

TypeScript strict

Mobile SDKs

Kotlin / Swift / Flutter

Server-to-server

fvk_… API key or JWT

2. BFF / edge (auth boundary)

Next.js route handler

httpOnly cookie → JWT bearer

CORS allow-list

baseline + env, never wildcard

Tenant-scoped rate limit

per employerId

3. Spring Boot 4 backend (default-deny)

JwtAuthenticationFilter

15-min refresh window

ApiKeyAuthenticationFilter

fvk_… for /v1/**

RBAC: ADMIN / HR_MANAGER / VIEWER

AuditLog interceptor

every mutating action

4. Persistence (multi-tenant by employer_id)

PostgreSQL 16

Railway managed, daily PITR backups

EncryptedStringConverter

AES-256-GCM per-field

HMAC blind index

email_hash for O(1) lookup

Outbox + poller

5 retries → DLQ

5. External providers (signed webhooks both ways)

Paystack · Yoco · PayFast · Ozow · Stitch · Rapyd

HMAC-SHA256/512 verify

BulkSMS · SMSPortal · Africa's Talking

token / signature

WhatsApp Cloud API

X-Hub-Signature-256

Stellar anchor (optional)

public receipt verifiability

3. Compliance posture — what we have, what we do not, by regulation

Honesty earns more trust than puffery. The "gap" column is the bit a regulated buyer cares about most; it is filled in for every row.

RegulationWhat it requiresWhat FinVeil has doneEvidenceGap
POPIA (Protection of Personal Information Act, 2013) — South AfricaPII protection, lawful processing, consent, breach notification.Every PII column is AES-256-GCM encrypted. Consent is tracked per-employee with version + timestamp. Audit log on every read/write. Data-subject-request workflow surfaced in /admin/compliance.EncryptedStringConverter.java; Employee.consent_status enum; AuditLogService.javaPOPIA Information Officer registration pending with the Regulator.
King IV (Corporate Governance) — South AfricaBoard governance, risk register, ethical decision-making.Risk register maintained internally; quarterly review cadence. Founder-led; advisory board formation under way.docs/governance/ (internal)Independent non-executive directors not yet appointed (pre-Series A milestone).
FICA (Financial Intelligence Centre Act) — South AfricaKYC, source-of-funds, sanctions screening on counterparties.Recipient verification flow operational at /admin/verifications. Beneficiary metadata captured before any disbursement leaves the platform.frontend/app/(app)/admin/verifications/Automated sanctions-list screening (OFAC / DFSA / SAMA) integration scoped, not yet shipped.
SARB (South African Reserve Bank) — regulatory ratesUse of current repo / prime / CPI rates in customer-facing calculations.Regulatory rate snapshots tracked and versioned; admin-only mutations behind ADMIN role. Public read endpoint surfaces freshness metadata.frontend/app/(app)/admin/regulatory-rates/; SecurityConfig.java:226 (/api/regulatory/public/**)Automated SARB scrape with provenance attestation is roadmap (currently human-validated entry).
PCI-DSSCard-data protection.FinVeil never stores or transits PAN/CVV. Card tokenisation handled by the underlying provider (Paystack / Yoco / PayFast). FinVeil holds only the provider-issued reference token.backend/src/main/java/io/finveil/orchestration/provider/adapters/Self-assessment Questionnaire (SAQ-A) available on request; no QSA attestation engaged.
GDPR — applicable in Mauritius via EU adequacy frameworkEU data protection norms; right to access, rectify, erase, port; lawful basis.Same control surface as POPIA covers all GDPR principles. Data is stored in eu-west Frankfurt today, so cross-border transfer mechanics are not yet triggered.EncryptedStringConverter.java; Privacy Policy at /legal/privacyEU DPO appointment deferred until first EU customer.
NCR (National Credit Regulator) — South AfricaCredit-agreement origination.FinVeil does not originate credit. Earned-wage advances route through registered credit providers who hold NCR licences. Affordability calculations follow NCR Regulation 23A.backend/src/main/java/io/finveil/disbursement/proof/ (affordability + receipt)NCR juristic-credit-provider registration in progress for future first-party offerings.

4. Data protection in detail

Per-field encryption

AES-256-GCM applied at the JPA layer via EncryptedStringConverter (line 41). Each ciphertext carries its own random 96-bit IV and 128-bit authentication tag — tamper detection is at the field level, not the disk level. A 32-byte key mismatch surfaces a sentinel in logs rather than crashing the request so the issue is investigable.

Searchable PII

For columns that need an equality lookup (e.g. users.email at login), we store an HMAC-SHA256 blind index in a sibling column — User.emailHash (User.java:38). The plaintext is never written to the database; only the constant-time HMAC is.

Multi-tenant isolation

Every tenant-scoped row carries an employer_id. All repository methods use findByIdAndEmployerId-shaped lookups so a controller cannot accidentally cross tenants. Example: EmployeeRepository.java:23. Cross-tenant guard rails are enforced both in JPA and at the controller boundary.

Key custody — honest

Today the JWT signing secret and the AES key (FINVEIL_ENCRYPTION_KEY) live in Railway environment variables — the standard 12-factor posture. This is the right answer for an early-stage platform; the enterprise next step is HashiCorp Vault or AWS KMS, which is a one-PR swap behind the resolveKey() method.

In-transit security

TLS 1.3 end-to-end. Let's Encrypt certificates provisioned and rotated automatically by the Railway and Vercel edge. HSTS, SameSite, and httpOnly are set on every cookie minted by the BFF route handlers; no JWT ever reaches the browser JS heap.

Backups & recovery

Daily PostgreSQL snapshots through Railway with point-in-time recovery over a 7-day window. RTO is measured in minutes for a single-region recovery. A staging restore drill is run quarterly against the production snapshot.

5. Operational readiness — what is in place today

  • 715 backend tests (JUnit 5) + 1 404 frontend test cases (Jest + RTL).
  • JaCoCo 50% line-coverage gate at the BUNDLE level — fails the build, not a warning. Exclusions are limited to live-credential-required adapters and listed explicitly in backend/pom.xml.
  • GitHub Actions CI: backend unit + integration, frontend tsc + jest, OWASP dependency-check, E2E smoke, SDK sync check, ISO-standards drift detection.
  • SDK ecosystem in 9 languages (Java, TypeScript/Node, Python, Go, Kotlin, Rust, C#, PHP, Flutter); 3 implemented to GA, 6 scaffolded against the published OpenAPI spec.
  • Cryptographic receipts on every disbursement; optional Stellar anchoring for third-party verifiability without sharing credentials.
  • /api/health and /actuator/health UP; /admin/operations surfaces the outbox dead-letter queue for human review.
  • Demo mode for partner walkthroughs that uses seeded data and short-circuits all live provider calls — Investec stakeholders can drive the platform without touching prod.

6. What is not yet in place

Investec will trust an honest gap list more than an aspirational one. Here is ours.

  • External penetration test — not yet conducted. Budget allocated for an accredited SA CREST firm; would commence on first enterprise sign-on.
  • SOC 2 Type I / ISO 27001 — not started. The control set above maps cleanly to both frameworks; we would engage Vanta / Drata in parallel with the first enterprise deal.
  • PCI-DSS QSA attestation — not applicable (no card data stored), but a written SAQ-A self-assessment is available under NDA.
  • POPIA Information Officer registration — pending with the Regulator; designation already complete internally.
  • Multi-region active/active — single-region (Frankfurt) today. Cape Town read-replica is on the roadmap; full active/active would follow a regulated multi-region customer commitment.
  • Hardware security module / Vault for key custody — current AES + JWT secrets are env-var-managed (the standard 12-factor pattern). HashiCorp Vault or AWS KMS is the explicit enterprise next step and is a one-PR swap behind the EncryptedStringConverter interface.

7. Why FinVeil for Investec Mauritius specifically

Adapter pattern means MauCAS / IPS is one implementation away

PaymentProviderAdapter is a sealed interface; Paystack, Yoco, PayFast, Ozow, Stitch and Rapyd each ship as ~300-line implementations. Adding MauCAS or Mauritius IPS is the same shape of work — no schema migration, no router rewrite.

20-currency reconciliation aligns with Investec Mauritius capability

Settlement and reconciliation entities carry an ISO-4217 currency column from day one. Multi-currency wallet UI ships in the demo; we are not retrofitting i18n on top of a ZAR-only core.

SA → MUR cross-border corridor architecture sound, awaits sponsorship

Disbursement routing already chooses between rails by cost, latency, and counterparty trust. A SA → MUR corridor adds a destination provider; the routing intelligence does not change. Investec Mauritius sponsorship into MauCAS is the missing piece.

Demo mode lets stakeholders evaluate before integration commitment

demo.finveil.money runs the full platform with seeded data and provider shims. Risk officers, compliance, and ops can drive flows end-to-end before any contract or production credential exchange.

Want the full compliance pack under NDA?

Architecture diagrams, the SAQ-A self-assessment, the POPIA impact assessment, the data-flow inventory, and the risk register are all available under NDA. We will respond within one business day.

Email zared@finveil.money