SMS Orchestrator — Security Model
Status: populated Owner: Platform Engineering + Security Last updated: 2026-04-18 Companion: 13 Security · ADR-0001 Kong
1. Authentication
Enforced at Kong:
jwtplugin (Firebase /auth-serviceJWKS) for user traffic.key-auth(or customghasi-api-key-lookup) plugin for machine traffic.
Service re-validates X-Tenant-Id header against JWT tenant_id claim — rejects mismatch with 403.
2. Authorization (RBAC/ABAC)
| Scope | Action | Role(s) |
|---|---|---|
sms:send | POST /v1/sms/send, /v1/sms/bulk | account.sender, account.admin |
sms:read | GET /v1/sms/{id} | account.reader, account.sender, account.admin |
| Admin cross-tenant read | Any message | platform.admin (via admin-dashboard, audit-logged) |
Role checks performed in service guards; scopes embedded in JWT.
3. Tenant Isolation
tenant_idcolumn + RLS policy on every table.- Query always includes
WHERE tenant_id = $1AND RLS; double defense. - Integration test
test/integration/tenant-isolation.spec.tsproves cross-tenant read returns 404.
4. Encryption
| Class | Mechanism |
|---|---|
| In-flight | TLS 1.2+ (Kong + internal mesh mTLS) |
| At rest (PG) | Transparent disk encryption (AWS EBS / GCP PD) |
Sensitive column (body) | Application-level field not required at current sensitivity class; revisit if regulated content categories are added |
| Secrets | Vault → env injected at pod start; no plaintext in env files |
5. Audit
Events written to sms.events.status are the audit trail. Administrative reads via admin-dashboard emit audit.access.v1 (owned by audit/ops, not this service).
6. Threat Model
| Threat | Control |
|---|---|
| Credential stuffing against API keys | Kong rate-limiting-advanced + account lockout in auth-service |
| Replay attacks | Idempotency-Key window; replay returns same messageId |
| Spam / abuse (high-volume sender) | Per-account + per-destination rate limits at Kong; future content classifier (AI) |
| PII leakage in logs | Body and MSISDNs hashed in Pino logs; forbidden in Kong access logs |
| DB SQL injection | Prisma parameterization + ESLint rule |
| Tenant spoofing | X-Tenant-Id re-checked against JWT claim; fail closed |
7. Data Residency + GDPR
tenant_id→ region mapping in tenant-service; this service runs per region.- GDPR erasure: on
tenant_erased.v1, this service redactsbody,to,from,metadataon matching rows (keepsmessageId+ timestamps + billing-required fields).