Skip to main content
Baobyte AML Suite

Security architecture

Run it in your own data centre, and be able to prove what it protects

The platform is provider-neutral by construction: PostgreSQL, an identity provider, an object store or a volume, a secret manager and TLS. That is the whole dependency list, which is what makes an on-premise or air-gapped installation a supported shape rather than a special case.

Nothing on this page is a certification claim. It describes controls and the evidence they produce. Attestation status, testing and disclosure live in the trust centre.

The problem

Three questions a Nigerian CISO asks first

And they are the right three, because each one closes off a whole class of vendor that cannot answer it.

  • Where does the data actually sit?

    Customer identifiers, CDD documents and investigation notes are among the most sensitive records an institution holds. Data residency is not a preference to be negotiated after signature; it decides the deployment shape, the contract and, frequently, whether a procurement can proceed at all.
  • What separates us from the next tenant?

    A shared-schema platform where isolation is a WHERE clause in application code is one missing predicate away from a leak between two competing banks. The question is not whether isolation exists, but how many independent things have to fail before it does not.
  • Who can read it if someone gets the database?

    A database dump is the realistic breach. If identifiers, documents and notes are readable from it, then every other control is a delay rather than a protection — and the answer has to be structural, not a promise about access management.

Deployment

Two supported shapes, and a recommendation

Everything downstream — object storage, replica counts, DNS, backup design — follows from this one decision, so it is made before anything is provisioned. For a first production install the single-node shape is the recommendation even on a cloud provider, because it is the one that has actually been drilled.

Single node

One institution, one site. A bank or microfinance bank on-premise, a single private-cloud host, or an air-gapped data centre.

Orchestration
Docker Compose on one Linux host
Tenancy
Fixed — one institution
API replicas
One, alongside exactly one worker
Evidence store
Filesystem volume, or self-hosted S3-compatible storage
Database
External PostgreSQL 16, managed or self-operated
Identity
Your provider, or a self-contained one inside the bundle
Air-gap
Supported — images load from the bundle, no registry needed

The drilled path. The air-gap installer verifies its own checksums and refuses to install an altered bundle, and the install drill is documented including what it has and has not proved.

Kubernetes

Multi-tenant hosting, or an institution that already operates a cluster to a production standard and wants horizontal API scale.

Orchestration
Helm chart, Kubernetes 1.27 or later
Tenancy
Fixed, subdomain or identity-claim resolution
API replicas
One or many, alongside exactly one worker
Evidence store
S3-compatible storage — required for multi-tenant or multi-replica
Database
External PostgreSQL 16 with the platform's own high availability
Identity
Your provider, with TLS ingress and wildcard certificates
Air-gap
Not the target shape

Stated honestly: the chart has been linted and rendered but has never been applied to a live cluster, and it carries that annotation in its own metadata. Budget for a first-install shake-out.

Both shapes run the same two images. One Go binary serves six run modes — the API, the worker, the migrator, a one-off bootstrap, an offline key generator and a standalone audit verifier — and the web interface is the second image. There is no service mesh requirement, no message broker, and no separate search or analytics engine to license, operate and patch.

The worker runs as exactly one instance and does not scale horizontally: its periodic jobs are declared per client, so a second replica duplicates every one of them. Capacity comes from its concurrency setting instead. That constraint is stated loudly in the operator documentation for a specific reason — a dead worker produces no visible symptom until transaction partitions stop being created and, once the pre-created window is exhausted, inserts begin to fail. Alerting on the worker heartbeat is a requirement, not a recommendation.

Data residency

Residency is decided by where you run two things

PostgreSQL and the evidence store hold everything that matters. Put both inside your jurisdiction and the residency question is answered structurally rather than contractually.

  1. Institution

    The system of record is your PostgreSQL instance

    Every customer, transaction, alert, case, report and audit event lives in one PostgreSQL 16 database that the institution provisions — managed by a cloud provider, self-operated on a host, or inside an air-gapped data centre. The application never creates it and holds no second copy elsewhere.
  2. Institution

    Evidence lives beside it, encrypted and write-once

    CDD documents, case attachments and authority-response exports are stored outside the database in a filesystem volume or an S3-compatible bucket you control. What the store receives is ciphertext and an opaque key: documents are encrypted under the subject key before the adapter sees them, and filenames are encrypted too, so they never appear in a URL or an object key. A compromised bucket credential yields unreadable bytes.
  3. Network team

    Outbound traffic is an allowlist, not a default

    The runtime needs to reach your identity provider, your object store, your image registry and — only where they are configured — an SMTP relay and a tracing collector. Nothing else. On an air-gapped site the registry requirement disappears because images load from the transferred bundle. The public marketing and product surfaces make no cross-origin requests at all, which is what keeps them usable on a disconnected network.
  4. Platform operator

    The provider matters mostly for the evidence store

    The write-once guarantee depends on the store honouring a conditional put, and that is not universal. One major cloud’s native blob service does not satisfy it and needs self-hosted S3-compatible storage instead; another requires the behaviour to be proven before selection. There is a four-step acceptance test to run against any candidate store before go-live, and it is cheap enough that skipping it means resting the write-once property of regulatory evidence on an assumption.
  5. Security and compliance

    Escrow the root key, or the backup is not a backup

    The root key is the root of the whole encryption hierarchy. It is generated offline before installation, held in an external secret manager, and never written to a values file, an image, a log line, a repository or a support bundle. A database dump without it is unreadable — so backing up the dump and not escrowing the key is not a backup, and the escrow procedure has to be documented and rehearsed rather than merely written down.

Tenancy

Five independent layers, because one is not a standard

Any single one of these would isolate two institutions from each other. They are all present because the consequence of a leak between two competing Nigerian banks is not a defect report.

  1. A tenant column on every scoped table

    Not nullable, and enforced structurally by a migration linter rather than by review.

  2. Composite foreign keys carrying the tenant

    A bug cannot attach one tenant's approval request to another tenant's user — the database refuses it. Row-level security would not catch this, because both rows are legitimately readable inside their own tenants.

  3. Row-level security, enabled and forced

    With both read and write policies, so a write that would land in the wrong tenant is refused rather than merely being invisible afterwards.

  4. A runtime role with no way out

    Tenant work connects as a role that is not the table owner, has no bypass on row-level security and is not a superuser. Estate metadata uses a second, small pool as a different role; migrations use a third.

  5. Explicit tenant predicates anyway

    Generated queries still carry the tenant condition. Redundant with row-level security on purpose — the redundancy is the point.

  6. One way to reach the database

    The data-access package exports a single tenant-scoped transaction helper. There is no exported pool and no exported connection, so omitting the tenant scope is not something a developer can express. The tenant marker is transaction-local, never session-level, because a pooled connection would otherwise hand the next caller the previous caller's tenant.

The isolation suite enumerates every table in the application schema from the live database catalogue at run time, seeds a row as one tenant and asserts a second tenant’s connection sees nothing and can neither update nor delete it. Because the table list is discovered rather than written down, a table added years from now is covered on its first run. The suite was itself verified against a deliberate regression: disabling row-level security on one table makes it report both the structural failure and the actual read and delete leak.

Encryption

Per-subject keys, built before any personal data existed

This shape cannot be retrofitted. Encrypting a book of customers under a shared key and later deciding you need per-subject erasure means re-encrypting everything, so the hierarchy was built first.

Three-tier envelope encryption with per-subject keysFour nested tiers. A root key held in an external secret manager derives a tenant key per institution, using the tenant identity as context. Each tenant key derives one data encryption key per data subject, and destroying that subject key is what performs an erasure. The subject key encrypts individual field values with AES-256-GCM, binding the field name into the authenticated data. Separately, searchable identifiers carry an HMAC blind index keyed by the tenant key, which permits equality lookup without decryption and is not comparable across tenants, so it cannot correlate a person between two institutions on the same installation.Root keyExternal secret manager. Lose it and the data is unrecoverable, by design.Tenant keyDerived per institution, with the tenant identity as context.Subject keyOne per data subject. Destroying it is the erasure.Field ciphertextAES-256-GCM, with the field name bound into the authenticated data.Blind index, keyed by the tenant keyEquality lookup on a BVN or NIN with no decryption.Not comparable across tenants, so it cannot correlateone person between two institutions.
Encrypted fields include identifiers, dates of birth, configured names and addresses, investigation notes, CDD document metadata and document bytes. Destroying a single subject's key renders that person's identity fields permanently unreadable while the transactional and audit skeleton that anti-money-laundering law requires survives — which is the only mechanism that answers a data-protection erasure right and a five-year retention floor at the same time.
  • Database privileges are split by job

    Runtime, platform and migration connections authenticate as different logins with different privileges, with a further, much narrower login for encrypted validation evidence. Startup rejects credentials that share a login, inherit another privileged role, own protected objects, bypass row-level security, hold administrative rights or fall outside the permitted privilege set.
  • Evidence storage has no delete operation

    The filesystem adapter commits with an atomic hard link; the S3 adapter sends a conditional put. An existing key is an error, never an overwrite, and the storage credential is granted list, get and put — and deliberately not delete.
  • Unsafe production configuration refuses to boot

    Missing public URL, unsafe authentication, an unsigned content pack, an empty root key, a missing dedicated platform connection, or object storage without TLS all stop the process before it listens, with an explicit message. A misconfigured deployment failing loudly is the fastest diagnostic available.
  • The runtime image has no shell

    The Go binary runs on a distroless static base as a non-root user with a read-only root filesystem. Conventional container debugging is unavailable by construction, so diagnostics are designed around logs, metrics and one-off jobs from the same image.
  • Telemetry carries no identifiers

    Metrics never label a tenant or a customer, access audit records metadata rather than URLs or query strings, and downloads are no-store. Security telemetry must not become a second customer database.
  • Content packs are signature-verified

    Regulatory content installs as a signed pack with manifest hashes and a completeness check, verified against a configured public key. In production an unsigned pack refuses to load, so policy content cannot be swapped without the release key.

In the product

The role and permission matrix

Isolation and encryption are properties of the architecture. Authorisation is a thing an administrator has to be able to read, review and hand to an auditor — so it is a screen rather than a configuration file.

Placeholder — TODO: capture the roles and permissions screen (/admin/roles) from a seeded demonstration tenant — no screenshot of it exists yet. Replace with sourced, approved content before this page is made public.

/admin/roles

roles and permissions

Screenshot slot. Capture from a seeded demonstration tenant with synthetic data and replace this frame. Do not substitute a mock-up of the interface.

Capture from a demonstration tenant with the standard persona roles installed. The capture must show the full catalogue rather than a filtered view: the permissions a role does not hold are the half of this screen an access review is actually for.

What the capture must show

  1. Every role against a closed catalogueThe permission catalogue is closed in code and every route's permission is registered and checked against the published API contract, so the matrix is the whole surface rather than a subset somebody remembered to document.
  2. Exclusions read as exclusionsFront office cannot approve its own onboarding, an analyst cannot file or close where the policy prohibits it, an executive cannot open customer content, and a service account cannot read content at all — each visible as an absence rather than inferred.
  3. AML policy separated from operationsSystem administration holds users, roles, organisational units and service accounts; it does not hold AML content by default. Platform operations holds tenant lifecycle and infrastructure, not routine customer content.
  4. A permission is necessary, not sufficientActor type, tenant, organisational unit, second-factor policy and workflow state are all checked alongside it, so a granted permission is not the end of the authorisation decision.
  5. An access-review artefactThis is the printout that ends up in the quarterly access-review file, alongside user status, role assignments, service credentials and open support sessions.
  6. Every change is in the audit chainRole creation, permission assignment and user activation are actor-initiated actions, so they are chained events rather than rows somebody could later adjust.

Where the edges are

What is built, what is drilled, and what is still owed

A security page that lists only controls is a marketing document. These are the items an institution's own security function will find, so they are better read here first.

Implemented and testable

  • Five-layer tenant isolation with a run-time-discovered isolation suite, and a data-access package that makes an unscoped query inexpressible.
  • Three-tier envelope encryption with per-subject keys, field-name-bound authenticated encryption, and tenant-keyed blind indexes that cannot correlate a person across tenants.
  • Write-once ciphertext evidence storage with no delete operation, digest-verified download and audited access.
  • Fail-closed production configuration validation, distroless non-root runtime images, split database credentials and signature-verified content packs.
  • Two deployment shapes including a checksum-verified air-gap bundle, and a standalone offline audit-verification mode.

Not yet evidenced, or not ours

  • A hardware security module or cloud key-management integration. Key material is read from the environment today, so a managed key service is an application change rather than a configuration choice — and root-key rotation rewraps subject keys without claiming physical retirement of the old root.
  • Kubernetes deployment evidence. The chart is linted and rendered and has never been applied to a live cluster; that is recorded in the chart's own metadata rather than discovered on install day.
  • Measured capacity. No load test has been run against this build. The sizing figures in the infrastructure documentation are extrapolated and are stated there as a provisioning starting point and a load-test target, not a service level.
  • An independent penetration test report, a completed disaster-recovery drill with a signed timing, and a production-like row-level-security acceptance run. All three are release-gate items on the institution's own environment.
  • Any certification or attestation. Standards status belongs in the trust centre, and nothing on this page should be read as one.

Keep reading

Related capabilities

The evidence controls this architecture carries, the work of standing it up, and everything it has to reach outside itself.

For your security review

We will answer the threat model line by line

Bring your architecture review questionnaire. The threat register, the isolation suite, the encryption hierarchy and the open items are all written down already — including the ones that are still outstanding.