AGOW Platform · Multi-tenant HR SaaS · South Africa
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.
§1 · Request flow & trust boundaries
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
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
| Domain | Control | State |
|---|---|---|
| MFA | Instance-wide enforced MFA — TOTP + passkey, email-OTP fallback. WhatsApp is never an MFA factor. | Enforced |
| Password policy | Minimum 12 chars, all character classes; lockout after 5 failed password or OTP attempts. | Enforced |
| Auth architecture | Self-hosted OIDC IdP; PKCE public client — no client secret exists to leak; refresh-token rotation. | Live |
| Tenant isolation | Schema-per-tenant PostgreSQL + per-request membership gate + scoped role system. | Structural |
| Admin separation | Founder portal isolated (own deploy, IdP client, cookies, IP gate); IdP console off the public internet. | Live |
| Encryption | TLS 1.2/1.3 + HSTS at edge; SSL forced to the database; storage encrypted at rest (DB, volumes). | Enforced |
| Edge defense | AWS WAF (§1) + application-layer rate limits. | Enforcing |
| Malware scanning | Every document upload lands in a quarantine prefix, is scanned, and only clean files are promoted. Proven with EICAR. | Live |
| Upload hardening | Byte-level file validation (client MIME never trusted, SVG rejected on raster surfaces); webhooks verify HMAC signatures and fail closed. | Enforced |
| Audit trail | Application 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 |
| Secrets | Encrypted 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 & DR | Managed 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 control | PR-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 |
| Network | App 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 |
| Monitoring | Error tracking on API + portal (environment/release tagged); threat detection (GuardDuty) account-wide; metrics plane on a private overlay only; cost anomaly alerts. | Live |
| POPIA | Primary 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 lifecycle | Live → Archived → Purged model with locked timelines; whole-organisation export ("Takeout") available from the moment cancellation is initiated. | Shipping |
§4 · Continuous assurance
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
Report suspected vulnerabilities to security@agow.io. We ask reporters to allow us reasonable time to remediate before disclosure.