Skip to main content

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-service the additional requirement is mTLS: the Kong upstream requires a client certificate whose SAN matches spiffe://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-Roles headers. The service does not parse JWT directly.
  • Tenant-portal surface (future) sets SET LOCAL app.current_tenant_id from X-Tenant-Id; RLS on cdr.records enforces 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=false is blocked by a start-up guard in any non-local environment.

1.3 Outbound — ATRA

ChannelAuth
SFTP (port 2222)Public-key auth; client private key in Vault, 7-day rotation overlap; host public-key fingerprint pinned by sha256
HTTPS fallbackmTLS 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.

RoleCapabilities
cdr:readRead CDRs (RLS-scoped), rollups, exports, delivery logs, adjustments, transparency anchors
cdr:verifyInvoke POST /v1/cdr/chain/verify; produce Merkle inclusion proofs
cdr:writeIssue single adjustments (CORRECTION, VOID); trigger export redrop; resolve quarantine
cdr:adminActivate schema variants; rotate signing keys; trigger export regeneration
cdr:rerate-bulkEnqueue bulk re-rate jobs; requires X-Approver-Jwt header (four-eyes) for > 100k impact
regulator-auditorRead-only on CDRs, chain verification, audit log, transparency anchors — external ATRA-side actor scope
platform.auditorRead-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/trigger with settlementDay < today-2).

Dual-approval writes are logged with both actor ids in cdr.audit.details.


3. Data protection

3.1 PII inventory & classification

FieldClassificationStorageTransit
cdr.records.msisdn_hash_to/fromINTERNALPlain bytea (one-way hash with per-tenant salt)TLS 1.3
cdr.msisdn_vault.msisdn_*_ctRESTRICTED (Class IV)AES-256-GCM; per-operator KEK in Vault TransitTLS 1.3
cdr.records.sender_id_rawCONFIDENTIALPlain; disk encryption + DB TDETLS 1.3
cdr.records.charge_amountCONFIDENTIALPlain; tenant-scoped RLSTLS 1.3
cdr.exports.signature_base64INTERNAL (non-secret)PlainTLS 1.3
cdr.audit.detailsCONFIDENTIALPlain; contains row hashes + operator ids; no PIITLS 1.3
Raw MSISDN in TAP encoder transient memoryRESTRICTEDOnly in process memory during encoding; never persisted outside msisdn_vaultN/A
TAP/RAP file bytesRESTRICTED (to ATRA; Class IV)Signed, Object-Locked in ghasi-cdr-exports S3TLS 1.3 + SFTP AES-256-GCM

3.2 Encryption keys

KeyStoreRotationPurpose
cdr-export-signer-v1 (Ed25519)HSM (PKCS#11; Thales Luna in prod, SoftHSMv2 in dev)Annual or on incident; 30-day dual-key overlapSign every TAP/RAP file
Per-operator KEK transit/ghasi-cdr-msisdn-{operatorId}Vault TransitAnnualWrap per-row DEKs for cdr.msisdn_vault
Per-row DEK (AES-256-GCM)Inline with ciphertext; unwrapped via Vault Transit per readImplicit per rowMSISDN payload encryption
Tenant-specific MSISDN-hash saltVault KVOn tenant creation; rotation not required (hash is one-way)Prevent cross-tenant rainbow attacks
mTLS server + client certs (internal)Vault PKI30 daysIntra-platform auth
ATRA SFTP key pairVault KV90 days with 7-day overlapATRA delivery channel
ATRA HTTPS mTLS client certVault PKI (ATRA-signed intermediate)365 daysATRA delivery fallback
ghasi-cdr-archive-kek (KMS key for S3 SSE-KMS)Cloud KMS (sovereign)AnnualArchive bucket envelope encryption
Outbox RS256 signing key (for downstream event verification)Vault KVAnnualDownstream consumer signature check
Transparency log signing keyTrillian operator sideExternalSLE signature

3.3 Redaction rules

  • In events. No raw MSISDN. Only msisdnHashTo/From (hex SHA-256). sender_id_raw may appear for internal consumers only (analytics, billing). Tenant-facing consumers see senderId masked to first 4 chars.
  • In logs. Pino redactor masks msisdn*, sender_id_raw, signature_base64, and forbids raw DLR body entirely. ESLint rule blocks logger.info(..., { msisdn }) at PR time.
  • In REST responses. regulator-auditor and cdr:admin see MSISDN in full (required for regulator queries). platform.auditor sees 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; the msisdn_vault encrypted 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_DETECTED fires, the export scheduler halts until Legal + Regulator Liaison clear the incident.
  • Sequence-gap rejection. If a file's fileSequenceNumber would not be previous+1, the delivery attempts abort with DUPLICATE_FILE rather 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.records keyed on tenant_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

SecretStoreInjection
HSM PINVault KV; mounted as tmpfs file /var/run/secrets/hsm-pinFile
Postgres credentialsVault DB dynamic secret (15-min TTL, auto-renewed)Env var
Redis credentialsVault KVEnv var
NATS credentialsVault KV; nats.creds fileFile mount
ATRA SFTP private keyVault KV → tmpfs /var/run/secrets/atra-sftp.keyFile
ATRA SFTP host fingerprintConfigMap (public, pinned)Env var
ATRA HTTPS client cert + keyVault PKI → tmpfs /var/run/secrets/atra-rest.*File
Transparency log access tokenVault KVEnv var
mTLS server + client certs (internal)Vault PKI via Vault AgentFile 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 dxb is 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

ThreatMitigation
Insider with DBA access silently modifies a CDRAppend-only rules on cdr.records; hash-chain nightly verifier catches any mutation within 24 h; alert triggers SEV1 + Legal
Insider deletes cold-archive objectsS3 Object-Lock Compliance mode — even root credentials cannot delete until retention expires
Compromised admin credentials activate a malicious schema variantFour-eyes on :activate; cdr.config.changed.v1 fans out to SOC; audit trail identifies both actors
Signing-key exfiltrationHSM-resident key never leaves the device; PKCS#11 sessions audited; HSM tampering triggers alarm
ATRA channel MITMSFTP host-key pinning + known-hosts file; HTTPS mTLS + ATRA CA pinning
Replay of an old signed TAP filefileSequenceNumber unique across (recordingEntity, exportType); ATRA-side check + local ux_exports_seq constraint
Adversary submits a CDR with fabricated source_event_id to poison chainIngest consumer trusts only sms.dlr.inbound events signed by dlr-processor's outbox key; cross-verified at consumer
Lateral movement via Postgres → bulk adjustment issuanceREST 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 hashPer-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 tamperingDaily 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.
  • 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 via SELECT 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-scanner dependency audit; trivy container scan.
  • HSM-mock chaos test — disconnect HSM during export run; verify exponential backoff + eventual SEV1.

12. Cross-References

End of SECURITY_MODEL.md