CDR Mediation Service — Security Model
Version: 1.0 Status: Draft Owner: Commerce + Regulator Liaison · Security Last Updated: 2026-04-21 Companion: API_CONTRACTS · DATA_MODEL · EVENT_SCHEMAS · ADR-0004 §11–§12
The CDR Mediation Service holds Class IV data (regulator-required, ≥ 7-year retention, non-erasable under ATRA obligation) and interfaces directly with ATRA — an external national authority. Its security posture is the strictest on the platform:
- Fail-closed on write path integrity (no unsigned exports leave the cluster).
- HSM-bound export signing keys.
- Append-only storage with hash chains at row, rollup, and daily transparency-log granularity.
- Per-operator encryption of raw MSISDNs at rest.
- mTLS everywhere the service speaks synchronously; SFTP with pinned host fingerprints and rotating key pairs.
1. Authentication
1.1 Inbound — REST admin + regulator
- Kong validates the platform JWT (RS256, JWKS-backed by
auth-service). Rejects unauthenticated at the edge. - For
regulator-portal-servicethe additional requirement is mTLS: the Kong upstream requires a client certificate whose SAN matchesspiffe://ghasi.af/svc/regulator-portal-service. Other callers' mTLS SVIDs are enforced similarly. - Kong forwards
X-Tenant-Id,X-Account-Id,X-User-Id,X-Rolesheaders. The service does not parse JWT directly. - Tenant-portal surface (future) sets
SET LOCAL app.current_tenant_idfromX-Tenant-Id; RLS oncdr.recordsenforces isolation.
1.2 Inbound — internal mTLS
billing-service(reconciliation reads),analytics-service(cold-tier export),regulator-portal-service— all use mTLS with client SVIDs issued by Vault PKI.- SAN allowlist per route; other identities rejected
UNAUTHENTICATED. - Client certs rotated every 30 days via Vault Agent Sidecar Injector; the server hot-reloads on file change.
- Local dev bypass via
TLS_ENABLED=falseis blocked by a start-up guard in any non-local environment.
1.3 Outbound — ATRA
| Channel | Auth |
|---|---|
| SFTP (port 2222) | Public-key auth; client private key in Vault, 7-day rotation overlap; host public-key fingerprint pinned by sha256 |
| HTTPS fallback | mTLS with regulator-issued client certificate; ATRA CA pinned |
Both channels carry explicit cert-expiry monitoring — cert expiring < 7 d triggers SEV2 CdrAtraCertExpiring.
1.4 IdP-agnostic
Per ADR-0002, the IdP that authenticated the human caller is irrelevant to cdr-mediation-service. idp claim captured in cdr.audit.details for forensics only.
2. Authorization (RBAC + four-eyes)
Role definitions live in auth-service; cdr-mediation-service enforces scope/role checks at the handler boundary via NestJS RoleGuard.
| Role | Capabilities |
|---|---|
cdr:read | Read CDRs (RLS-scoped), rollups, exports, delivery logs, adjustments, transparency anchors |
cdr:verify | Invoke POST /v1/cdr/chain/verify; produce Merkle inclusion proofs |
cdr:write | Issue single adjustments (CORRECTION, VOID); trigger export redrop; resolve quarantine |
cdr:admin | Activate schema variants; rotate signing keys; trigger export regeneration |
cdr:rerate-bulk | Enqueue bulk re-rate jobs; requires X-Approver-Jwt header (four-eyes) for > 100k impact |
regulator-auditor | Read-only on CDRs, chain verification, audit log, transparency anchors — external ATRA-side actor scope |
platform.auditor | Read-only on cdr.audit, cdr.export_delivery_log, cdr.adjustments — internal platform auditor |
Four-eyes (dual control) triggers. The following operations require a second X-Approver-Jwt header whose subject differs from the primary actor:
- Bulk re-rate with estimated impact > 100,000 CDRs (see UC-06).
POST /v1/cdr/regulator-schemas/{variant}:activate(schema cutover).- Signing-key rotation (
cdr-export-signer-vN). - Emergency re-encoding of a prior day's TAP file (
POST /v1/cdr/exports/triggerwithsettlementDay < today-2).
Dual-approval writes are logged with both actor ids in cdr.audit.details.
3. Data protection
3.1 PII inventory & classification
| Field | Classification | Storage | Transit |
|---|---|---|---|
cdr.records.msisdn_hash_to/from | INTERNAL | Plain bytea (one-way hash with per-tenant salt) | TLS 1.3 |
cdr.msisdn_vault.msisdn_*_ct | RESTRICTED (Class IV) | AES-256-GCM; per-operator KEK in Vault Transit | TLS 1.3 |
cdr.records.sender_id_raw | CONFIDENTIAL | Plain; disk encryption + DB TDE | TLS 1.3 |
cdr.records.charge_amount | CONFIDENTIAL | Plain; tenant-scoped RLS | TLS 1.3 |
cdr.exports.signature_base64 | INTERNAL (non-secret) | Plain | TLS 1.3 |
cdr.audit.details | CONFIDENTIAL | Plain; contains row hashes + operator ids; no PII | TLS 1.3 |
| Raw MSISDN in TAP encoder transient memory | RESTRICTED | Only in process memory during encoding; never persisted outside msisdn_vault | N/A |
| TAP/RAP file bytes | RESTRICTED (to ATRA; Class IV) | Signed, Object-Locked in ghasi-cdr-exports S3 | TLS 1.3 + SFTP AES-256-GCM |
3.2 Encryption keys
| Key | Store | Rotation | Purpose |
|---|---|---|---|
cdr-export-signer-v1 (Ed25519) | HSM (PKCS#11; Thales Luna in prod, SoftHSMv2 in dev) | Annual or on incident; 30-day dual-key overlap | Sign every TAP/RAP file |
Per-operator KEK transit/ghasi-cdr-msisdn-{operatorId} | Vault Transit | Annual | Wrap per-row DEKs for cdr.msisdn_vault |
| Per-row DEK (AES-256-GCM) | Inline with ciphertext; unwrapped via Vault Transit per read | Implicit per row | MSISDN payload encryption |
| Tenant-specific MSISDN-hash salt | Vault KV | On tenant creation; rotation not required (hash is one-way) | Prevent cross-tenant rainbow attacks |
| mTLS server + client certs (internal) | Vault PKI | 30 days | Intra-platform auth |
| ATRA SFTP key pair | Vault KV | 90 days with 7-day overlap | ATRA delivery channel |
| ATRA HTTPS mTLS client cert | Vault PKI (ATRA-signed intermediate) | 365 days | ATRA delivery fallback |
ghasi-cdr-archive-kek (KMS key for S3 SSE-KMS) | Cloud KMS (sovereign) | Annual | Archive bucket envelope encryption |
| Outbox RS256 signing key (for downstream event verification) | Vault KV | Annual | Downstream consumer signature check |
| Transparency log signing key | Trillian operator side | External | SLE signature |
3.3 Redaction rules
- In events. No raw MSISDN. Only
msisdnHashTo/From(hex SHA-256).sender_id_rawmay appear for internal consumers only (analytics, billing). Tenant-facing consumers seesenderIdmasked to first 4 chars. - In logs. Pino redactor masks
msisdn*,sender_id_raw,signature_base64, and forbids raw DLR body entirely. ESLint rule blockslogger.info(..., { msisdn })at PR time. - In REST responses.
regulator-auditorandcdr:adminsee MSISDN in full (required for regulator queries).platform.auditorsees MSISDN masked to country code + first 3 digits:+937*** ***. - In S3 archive. MSISDNs appear in TAP-encoded output only (regulator obligation). The JSONL archive stores
msisdnHash*hex; themsisdn_vaultencrypted rows are archived separately with retention aligned.
3.4 HSM-bound signing (PKCS#11 detail)
Every TAP/RAP file is signed inside the HSM without the private key ever leaving the appliance. Reference flow:
C_Initialize(null)
C_OpenSession(slot=0, CKF_SERIAL_SESSION|CKF_RW_SESSION)
C_Login(CKU_USER, pin_from_vault_tmpfs)
templates = [
{ CKA_CLASS: CKO_PRIVATE_KEY },
{ CKA_LABEL: "cdr-export-signer-v1" }
]
hKey = C_FindObjects(templates, 1)
C_SignInit(hKey, CKM_EDDSA)
signature = C_Sign(sha256(file_bytes))
C_Logout()
C_CloseSession()
Fallback mechanism on older HSMs lacking CKM_EDDSA: CKM_EC_EDWARDS. Key import is prohibited — keys are generated inside the HSM at provisioning time with:
C_GenerateKeyPair(
CKM_EC_EDWARDS_KEY_PAIR_GEN,
pubTemplate, privTemplate
)
The public key is exported once, published to ATRA, and persisted in cdr.regulator_schemas.adapter_class metadata for downstream verification.
3.5 Signed-file format (PKCS#7 sidecar option)
The default sidecar is plain text (KeyId, Algorithm, Signature, FileSha256, SignedAt). ATRA may require PKCS#7 / CMS in future:
CONTENT-TYPE: application/pkcs7-signature
Content-Transfer-Encoding: base64
MIIExAYJKoZIhvcNAQcCoIIEtTCCBLECAQExCTAHBgUrDgMCGjAcBgkqhkiG9w0B...
The service's sidecar emitter is pluggable per schemaVariant; switching to PKCS#7 is a deploy-time config change.
4. Audit
Every state change — adjustment issuance, export redrop, schema activation, signing-key rotation, delivery failure — writes a row to cdr.audit with entryType, operator, before/after, trace id, IP. Table is append-only at the DB level (Postgres DO INSTEAD NOTHING rule; see DATA_MODEL §3). Retention ≥ 13 months hot + permanent cold.
cdr.audit.v1 NATS event mirrors audit rows for SIEM and analytics-service consumption.
5. Fail-closed posture
- Exports pause on HSM unavailability. No unsigned file is ever produced.
- Chain-break detection paused new exports. Once
cdr.audit entryType=CHAIN_BREAK_DETECTEDfires, the export scheduler halts until Legal + Regulator Liaison clear the incident. - Sequence-gap rejection. If a file's
fileSequenceNumberwould not be previous+1, the delivery attempts abort withDUPLICATE_FILErather than risk ATRA-side rejection cascade. - Shadow-mode exports during onboarding. Per MIGRATION_PLAN Phase 1, the service generates CDRs without exporting for 30 days; mis-configurations that would have caused regulator penalties are caught before any file leaves the cluster.
6. Tenant isolation
- Postgres RLS on
cdr.recordskeyed ontenant_id. - Redis caches are keyed by
{tenantId}where tenant-scoped. - Per-tenant MSISDN-hash salt ensures a salt compromise is scoped.
- The service is platform-scoped for ATRA-facing operations — there is no tenant-scoped role that can trigger exports or issue adjustments affecting other tenants.
7. Secrets management
| Secret | Store | Injection |
|---|---|---|
| HSM PIN | Vault KV; mounted as tmpfs file /var/run/secrets/hsm-pin | File |
| Postgres credentials | Vault DB dynamic secret (15-min TTL, auto-renewed) | Env var |
| Redis credentials | Vault KV | Env var |
| NATS credentials | Vault KV; nats.creds file | File mount |
| ATRA SFTP private key | Vault KV → tmpfs /var/run/secrets/atra-sftp.key | File |
| ATRA SFTP host fingerprint | ConfigMap (public, pinned) | Env var |
| ATRA HTTPS client cert + key | Vault PKI → tmpfs /var/run/secrets/atra-rest.* | File |
| Transparency log access token | Vault KV | Env var |
| mTLS server + client certs (internal) | Vault PKI via Vault Agent | File mount |
No secret is ever written to logs, events, or config files. Pre-commit gitleaks scan blocks accidental commits.
8. Data residency
Per ADR-0004 §14, all CDR data originates and resides in-country (Kabul / Mazar) with async DR to dxb (sovereign cloud). Mandatory rules:
- No cross-border access to raw CDRs by any third party (including Ghasi corporate devices outside Afghanistan) without a Legal-approved break-glass procedure.
- DR replica in
dxbis read-only by policy — cannot serve write traffic without a dual-approved failover runbook execution. - Backups encrypted with sovereign-cloud-hosted KMS; restore tested quarterly.
9. Threat model
| Threat | Mitigation |
|---|---|
| Insider with DBA access silently modifies a CDR | Append-only rules on cdr.records; hash-chain nightly verifier catches any mutation within 24 h; alert triggers SEV1 + Legal |
| Insider deletes cold-archive objects | S3 Object-Lock Compliance mode — even root credentials cannot delete until retention expires |
| Compromised admin credentials activate a malicious schema variant | Four-eyes on :activate; cdr.config.changed.v1 fans out to SOC; audit trail identifies both actors |
| Signing-key exfiltration | HSM-resident key never leaves the device; PKCS#11 sessions audited; HSM tampering triggers alarm |
| ATRA channel MITM | SFTP host-key pinning + known-hosts file; HTTPS mTLS + ATRA CA pinning |
| Replay of an old signed TAP file | fileSequenceNumber unique across (recordingEntity, exportType); ATRA-side check + local ux_exports_seq constraint |
Adversary submits a CDR with fabricated source_event_id to poison chain | Ingest consumer trusts only sms.dlr.inbound events signed by dlr-processor's outbox key; cross-verified at consumer |
| Lateral movement via Postgres → bulk adjustment issuance | REST is the only write path; DB credentials are dynamic + short-lived; cdr-admin role requires JWT + four-eyes on bulk |
| Privacy attack: reverse-correlate MSISDN hash | Per-tenant salt; attacker with one tenant's CDRs cannot correlate across tenants; hash alone is not regulator-submittable (MSISDN is stored encrypted in msisdn_vault with per-operator KEK) |
| Transparency log tampering | Daily anchor to external Trillian-style log; external auditors can verify without trusting Ghasi infrastructure |
10. GDPR & regulatory
- Right to erasure. Under ATRA regulation, CDRs for 7 years are required to be retained and override the erasure right for the specific fields mandated by law. On
auth.user.erased.v1:- Redact
sender_id_raw(if the sender was the user's alpha ID and is no longer in use) — replace with<redacted>per regulator guidance. - Do not delete the CDR row or MSISDN ciphertext — these are regulator evidence.
- Redact
- Data portability. Tenants can export their own compliance-scoped CDR metadata via
/v1/cdr/records?tenantId=...(RLS-gated; MSISDN masked). - Sub-processor list. ATRA (receiver) is not a sub-processor; it is a regulator. No cloud LLM sub-processor exists (AI-free service — see AI_INTEGRATION).
- Audit evidence window. 7 years + transparency log anchor permanent. ATRA receives evidence via TAP/RAP files + audit-log exports on request.
11. Security testing
- Contract tests per API_CONTRACTS §8.
- Hash-chain tamper tests — attempt UPDATE on
cdr.records, verify rejection; attempt row-level edit viaSELECT FOR UPDATE, verify append-only rule triggers. - SFTP credential rotation test — simulate key rotation window, verify 7-day overlap.
- ATRA-side schema injection test — post malformed schema response, verify service rejects without crash.
- Role matrix test — every endpoint × every role — verifies 200/403 behaviour.
- Penetration test quarterly on the REST surface.
- Gitleaks secret scan in CI;
osv-scannerdependency audit;trivycontainer scan. - HSM-mock chaos test — disconnect HSM during export run; verify exponential backoff + eventual SEV1.
12. Cross-References
- API_CONTRACTS.md §2 — auth + RBAC per endpoint
- DATA_MODEL.md §3 + §6 — append-only + RLS + retention
- EVENT_SCHEMAS.md §5 — PII rules in events
- FAILURE_MODES.md — fail-closed behaviour per failure class
- ADR-0004 §11–§12 — HSM + PKI
- ADR-0002 — IdP-agnostic auth
docs/security/data-classification.md— Class IV retention- compliance-engine/SECURITY_MODEL.md — sibling fail-closed model
End of SECURITY_MODEL.md