AGOW AGOW

AGOW Platform · Multi-tenant HR SaaS · South Africa

Architecture & Security Posture

How a request reaches AGOW and what protects it at every hop; the security controls in force with their evidence; how customer data stays portable and exportable; and how the platform is continuously assured.

Reviewed 2026-07-19 Region af-south-1 (Cape Town) Compliance frame POPIA
≤ 5 min
Backup RPO (RDS PITR)
~24 min
Drilled restore RTO
Enforced
MFA · staging + prod
TLS 1.2/1.3
Edge · HSTS · force-SSL DB
100%
Changes via PR + CI gates

§1 · Request flow & trust boundaries

One hardened path in — nothing else listens

DNS points at a regional load balancer that terminates TLS and runs AWS WAF. That balancer is the only ingress: the application host accepts traffic solely from it, has no public ports and no SSH (session access via AWS SSM only). Inside, Caddy routes by hostname to isolated containers; data lives in a private, encrypted PostgreSQL (RDS) and in S3 reached only through short-lived presigned URLs. Administrative surfaces ride a private mesh overlay, never the public edge.

flowchart TB
    subgraph internet["Internet — untrusted"]
        U["Browser / Webhooks"]
    end
    subgraph edge["Edge · AWS af-south-1"]
        ALB["ALB — TLS 1.2/1.3 · HTTP→HTTPS"]
        WAF["AWS WAF — geo ZA+GB · IP reputation
OWASP CRS · known-bad inputs · rate limit"] ALB --- WAF end subgraph box["Application host — ingress from ALB only · no SSH"] CADDY["Caddy :80 — host routing
security headers"] PORTAL["Tenant portal
Next.js"] FOUNDER["Founder portal — isolated
own OIDC client + cookies + IP gate"] API["API — Django REST"] ZIT["ZITADEL — OIDC IdP
console: private mesh only"] CADDY --> PORTAL CADDY --> FOUNDER CADDY --> API CADDY --> ZIT PORTAL --> API FOUNDER --> API end subgraph data["Data plane — private"] RDS["RDS PostgreSQL 17
encrypted · SSL forced · PITR ≤5 min
prod Multi-AZ"] S3["S3 vault — presigned URLs only
upload malware quarantine"] end U -->|HTTPS 443| ALB ALB -->|in-VPC| CADDY API --> RDS ZIT --> RDS API --> S3

All traffic passes AWS WAF at the load balancer: managed IP-reputation and known-exploit rule sets, the OWASP Core Rule Set, geographic restriction, per-IP rate limiting, and an internet-blocked identity-provider console (administrative access is via a private overlay network only).

Identical rule set on staging and production. Defense in depth behind it: per-IP anonymous throttling and public-write throttling in the application itself.

sequenceDiagram
    participant B as Browser
    participant P as Portal (NextAuth)
    participant Z as ZITADEL (OIDC)
    participant A as API (Django)
    B->>P: visit tenant subdomain
    P->>Z: OIDC authorize — PKCE, no client secret
    Z-->>P: tokens (refresh rotation)
    Note over Z: MFA enforced (TOTP / passkey / email OTP)
lockout after 5 attempts · 12+ char passwords P->>A: Bearer token A->>A: token validation → tenant-membership gate A->>A: schema-per-tenant isolation (PostgreSQL)

Tenant isolation is structural: every customer organisation lives in its own PostgreSQL schema, selected by subdomain and enforced by a membership gate on every API request — not row-level filters. The founder (super-admin) surface is a separately deployed application with its own identity client and cookies, so tenant-surface compromise does not reach it.

§2 · Data portability & exit

Your data is yours — standard formats, exportable at any time

A whole-organisation export ("Takeout") is available on demand and is a first-class step at cancellation. Customer data lives in standard, open formats — PostgreSQL for records, ordinary object storage for documents — with no proprietary lock-in of customer data.

§3 · Security controls

Controls, in the language of a security questionnaire

DomainControlState
MFAInstance-wide enforced MFA — TOTP + passkey, email-OTP fallback. WhatsApp is never an MFA factor.Enforced
Password policyMinimum 12 chars, all character classes; lockout after 5 failed password or OTP attempts.Enforced
Auth architectureSelf-hosted OIDC IdP; PKCE public client — no client secret exists to leak; refresh-token rotation.Live
Tenant isolationSchema-per-tenant PostgreSQL + per-request membership gate + scoped role system.Structural
Admin separationFounder portal isolated (own deploy, IdP client, cookies, IP gate); IdP console off the public internet.Live
EncryptionTLS 1.2/1.3 + HSTS at edge; SSL forced to the database; storage encrypted at rest (DB, volumes).Enforced
Edge defenseAWS WAF (§1) + application-layer rate limits.Enforcing
Malware scanningEvery document upload lands in a quarantine prefix, is scanned, and only clean files are promoted. Proven with EICAR.Live
Upload hardeningByte-level file validation (client MIME never trusted, SVG rejected on raster surfaces); webhooks verify HMAC signatures and fail closed.Enforced
Audit trailApplication audit log (actor, action, before/after, IP; 7-year default retention); super-admin support sessions are time-limited, reason-logged, per-action audited; IdP auth events reviewable live.Active
SecretsEncrypted parameter store, rendered at boot; no secrets in the repository (blocking secret-scan on every PR); CI holds no long-lived cloud keys (OIDC).Enforced
Backup & DRManaged point-in-time recovery, RPO ≤5 min, 7-day retention, production Multi-AZ, deletion protection. Restore drilled: ~24 min measured RTO with data verification. Scenario-complete recovery runbooks.Drilled
Change controlPR-only mainline; required CI: full API test suite from zero, lint/build, dependency audit, blocking SAST; infrastructure changes need environment approval + named-approver allowlist.Enforced
NetworkApp host reachable only from the load balancer; no SSH (session-manager access); egress port-allowlisted; DB reachable only from the app host; reject-traffic flow logs retained.Enforced
MonitoringError tracking on API + portal (environment/release tagged); threat detection (GuardDuty) account-wide; metrics plane on a private overlay only; cost anomaly alerts.Live
POPIAPrimary personal data resides in af-south-1 by choice (POPIA §72 permits lawful transfer — residency is not mandated). Cross-border sub-processors (email, SMS, error tracking) operate under §72. Substance = §19: the access, encryption and audit controls above.Positioned
Data lifecycleLive → Archived → Purged model with locked timelines; whole-organisation export ("Takeout") available from the moment cancellation is initiated.Shipping

§4 · Continuous assurance

How this posture stays true

Restore drills run on a standing cadence against the measured recovery time above. Every change reaches production through a pull request gated by a full test suite and blocking static security analysis. A POPIA-aligned data-lifecycle programme governs retention and deletion. A security whitepaper and SOC 2 Type I preparation are underway.

Reporting a security concern

Found something? Tell us.

Report suspected vulnerabilities to security@agow.io. We ask reporters to allow us reasonable time to remediate before disclosure.