Skip to main content

Number Intelligence Service — Security Model

Version: 1.0 Status: Draft Owner: Messaging Core + Security Last Updated: 2026-04-21 Companion: API_CONTRACTS · DATA_MODEL · EVENT_SCHEMAS · 13 Security, Compliance, Tenancy · ADR-0004 §14

1. Authentication

1.1 gRPC plane — NumberIntelligenceService

  • mTLS + SPIFFE required. The gRPC server accepts only client certificates signed by the platform CA and presenting a SPIFFE SVID in the allowlist:
    • spiffe://ghasi.platform/ns/routing/sa/routing-engine
    • spiffe://ghasi.platform/ns/firewall/sa/sms-firewall-service
    • spiffe://ghasi.platform/ns/compliance/sa/compliance-engine
    • spiffe://ghasi.platform/ns/router/sa/channel-router-service
    • spiffe://ghasi.platform/ns/fraud/sa/fraud-intel-service
    • spiffe://ghasi.platform/ns/orchestrator/sa/sms-orchestrator
    • spiffe://ghasi.platform/ns/gateway/sa/tenant-sdk-gateway (tenant gRPC SDK — metered)
  • Client certs are issued from Vault PKI via the Vault Agent Sidecar Injector, rotated every 30 days; the server hot-reloads on file change.
  • Internal calls from ni-hlr-gateway DaemonSet pods to the main gRPC API use a dedicated SPIFFE ID spiffe://ghasi.platform/ns/numint/sa/hlr-gateway; the gateway itself authenticates outbound per-MNO endpoints with either SS7 M3UA (no app-layer auth; network-layer SSCTP security) or REST bearer JWT issued via client-credentials.
  • Local-dev TLS bypass via GRPC_TLS_ENABLED=false is prohibited in any non-local environment. Startup guard refuses to boot with TLS disabled when NODE_ENV != 'development'.

1.2 REST plane — admin, tenant, regulator

  • Kong validates the platform JWT (issued by auth-service, RS256, JWKS-backed). Requests without a valid token are rejected at the edge.
  • Kong forwards X-Tenant-Id, X-Account-Id, X-User-Id, X-Roles headers. number-intelligence-service never parses platform JWTs directly.
  • For tenant Public Lookup API endpoints (/v1/lookup/*), the service reads X-Tenant-Id and enforces TenantLookupQuota before processing. Lookup audit rows (numint.lookup_audit) use per-tenant-salt MSISDN hashing.
  • Per-tenant gRPC SDK uses the same mTLS server posture with a tenant-sdk-gateway SPIFFE ID; the gateway re-issues per-tenant identity as signed X-Tenant-Id to NI (gateway-signed HMAC over the tenant claim).

1.3 IdP-agnostic

Per ADR-0002, the identity provider (Keycloak / tenant OIDC / tenant SAML / Firebase legacy) is irrelevant to NI. The idp claim is captured in numint.lookup_audit.actor_sub prefix for forensics only.

2. Authorization (RBAC)

RoleCapabilities
platform.numint.adminFull admin: MNP conflict resolution, MNP on-demand ingest, EIR resync, adapter config, manual NumberRecord overrides (dual-control — requires X-Approver-User-Id), tenant quota overrides
platform.regulatorRead-only on MNP history, MNP chain-verification, MNP audit, EIR; cannot mutate
platform.supportRead-only attribution lookup for troubleshooting (MSISDN masked); view cache / HLR probe metrics
Tenant numint:lookupCall GET /v1/lookup/{msisdn} and POST /v1/lookup/batch for own tenant; subject to quota
Tenant numint:lookup_bulkAll numint:lookup + POST /v1/lookup/bulk-csv
Tenant numint:audit_readRead own tenant's lookup_audit rows for the past 90 days
Internal services (via SPIFFE SAN)gRPC calls; not metered; not subject to per-tenant quota

Enforcement points (in order):

  1. Kong — JWT validation, consumer.acl_groups, declarative scope check; rejects at edge.
  2. SPIFFE SAN guard (gRPC only) — server rejects peers not in the allowlist.
  3. NestJS RoleGuard — runs first inside the service; rejects with 403 INSUFFICIENT_SCOPE before handler entry.
  4. @RequireRoles(...) decorator — declarative; contract-tested.
  5. Tenant quota middleware — RPS + monthly quota enforcement; 429 before handler work.
  6. Postgres RLS on lookup_audit — defence in depth for tenant self-service reads.

3. Data protection

3.1 PII inventory & classification

FieldClassificationStorageTransit
numint.number_records.e164INTERNAL-PLATFORMMSISDN-to-MNO mapping is public telecom fact (an E.164 prefix table analogue); plaintext retained for SQL joinability with MNO prefix lookups; protected by authenticated API access + encryption-at-rest (per platform 13-sct §3); not classified as subscriber PII per ATRA guidance on carrier attributionTLS 1.2+
numint.number_records.msisdn_hashINTERNAL`sha256(e164
numint.portability_history.msisdn_hashINTERNALSame as aboveTLS 1.2+
numint.lookup_audit.msisdn_hashINTERNAL`sha256(e164
numint.eir_records.imeiRESTRICTEDOptional plaintext retained for admin forensics; hash is the primary keyTLS 1.2+
numint.eir_records.imei_hashINTERNAL`sha256(imei
numint.hlr_probes.result_snapshotINTERNALJSONB with MSISDN-hashes only; VLR plaintext; IMSI-prefix plaintext (MCC+MNC public telecom fact)TLS 1.2+
HLR PCAP samplesRESTRICTEDEncrypted with KMS key numint-pcap-kek; retained 90 days in MinIOTLS 1.2+ to MinIO
Per-MNO REST adapter JWTRESTRICTEDVault KV; short-lived (≤ 1 h)TLS 1.2+
SFTP keys (MNO MNP, ATRA CEIR)RESTRICTEDVault KVSSH

3.2 Encryption keys

KeyStoreRotation
Platform MSISDN pepperVault KV (secret/ghasi/numint/msisdn_pepper)Quarterly; envelope re-hash via background job; pepper_version tracks each row
Platform IMEI pepperVault KV (secret/ghasi/numint/imei_pepper)Quarterly
Per-tenant audit saltVault KV (secret/ghasi/numint/tenant-salts/{tenantId})Annual; or on tenant request / incident
PCAP KEKVault Transit (transit/ghasi-numint-pcap-kek)Annual
Audit chain signing keyVault Transit (transit/ghasi-numint-audit-signing)Annual; key version embedded per row
mTLS server + client certsVault PKI (pki/ghasi-numint)30 days
Postgres credentialsVault DB dynamic secret24 h TTL
Redis credentialsVault KVQuarterly
NATS credentialsVault KVQuarterly
Per-MNO REST adapter client JWTVault KVShort-lived (≤ 1 h)
MNO MNP SFTP keysVault KVAnnual or on regulator/MNO request

3.3 Redaction rules

  • In events. MSISDN appears only as msisdnHash and (where policy permits) msisdnMasked (+CC NNN ***). Raw MSISDN never appears on NATS.
  • In logs. Pino redactor masks msisdn, imei, vlr, imsi_prefix plaintext in application logs; an ESLint rule (no-pii-in-log) blocks logger.*({msisdn: …}) patterns at PR time.
  • In REST responses. The Public Lookup API does return raw MSISDN — the tenant submitted it, so echoing it is safe. Admin responses mask MSISDN unless the caller has platform.numint.admin role.
  • In audit payloads. lookup_audit.msisdn_hash (tenant-salted) only; raw MSISDN never recorded in audit.
  • In the LLM pipeline (per AI_INTEGRATION §2.3). Raw MSISDN replaced with indexed token before any LLM call.

3.4 Canonical serialisation for audit hash

  • RFC 8785 JSON Canonicalization Scheme (sorted keys, UTF-8 NFC, no insignificant whitespace).
  • Field ordering deterministic.
  • signing_key_id embedded per row; verifier handles multi-version chains through rotations.

3.5 Data residency

  • All numint schema rows, NATS subjects, Redis cache, and Vault namespaces reside in Afghan regions only (Kabul + Mazar active-active; Dubai cold-DR holds only AES-GCM-wrapped PG backups whose unwrap key never leaves Kabul HSM). Per ADR-0004 §14.
  • SS7 SIGTRAN and MNO REST endpoints are per-MNO in-country by construction (Afghan MNOs).
  • MinIO buckets for MNP raw archive, HLR PCAP, and audit cold archive all in af-kabul-1 with sync replication to af-mzr-1.
  • A deploy-time residency test (tests/residency/numint_residency.spec.ts) asserts every configured external endpoint resolves to Afghan IP space (MNO endpoints) or platform-internal (Vault, Redis, Postgres, NATS). Any offshore IP → deploy blocked.

4. Audit

  • numint.lookup_audit captures every Public Lookup API call with hash-chained integrity; internal gRPC calls are not in the audit store (they appear in structured logs + Prometheus only).
  • numint.audit_log captures administrative state changes (MNP conflict resolution, adapter config change, manual NumberRecord override, quota override).
  • Daily hash-chain verifier runs at 04:30 Asia/Kabul (see APPLICATION_LOGIC UC-AuditChainVerify); NumIntAuditChainBroken CRITICAL on any discovered break.
  • Regulator SIEM receives a mirror of numint.audit.v1 + numint.mnp.changed.v1 + numint.reconciliation.completed.v1 via regulator-portal-service.
  • consent-ledger-service uses a similar hash-chain design — implementations share the ChainVerifier library for consistency.

5. Fail-degraded posture (not fail-closed)

Unlike consent-ledger-service / compliance-engine, number-intelligence-service is fail-degraded:

  • On PG outage: serve from Redis; return confidence = LOW.
  • On Redis outage: serve from PG; latency degrades; confidence unchanged.
  • On both PG + Redis out: serve from prefix-table; source = PREFIX_FALLBACK, confidence = UNKNOWN.
  • On live HLR gateway down: return last-known persisted row; confidence = LOW.
  • On MNP reconciliation feed stale: serve last-known MNP state; NumIntMnpReconciliationStale alert fires.

Security-wise, fail-degraded means attribution can momentarily be wrong — but it is never catastrophically unavailable. Downstream callers have their own fallbacks (prefix table on routing-engine; geo-default on compliance-engine). Availability attacks cannot mis-route at scale because routing-engine's prefix-table fallback is deterministic and defensible.

The single hard exception: LookupEir on an IMEI that would be BLACKLIST but cannot be served (EIR store completely unreachable) returns UNKNOWN — the caller (sms-firewall-service) must then apply its own default policy, which for BLACKLIST-critical traffic is configurable per tenant as FAIL_CLOSED_ON_UNKNOWN.

6. Tenant isolation

  • Per-tenant salt on lookup_audit.msisdn_hash ensures two tenants looking up the same MSISDN produce different audit hashes.
  • Per-tenant Redis quota keys (numint:quota:lookup:{tenantId}:{yyyymm}) isolate quota accounting.
  • Public Lookup responses include no cross-tenant data — only the attribution fact for the MSISDN the tenant submitted.
  • Cross-tenant leakage tests: integration suite asserts tenant A cannot read tenant B's lookup_audit rows even when both look up the same MSISDN; RLS policy verified.

7. Secrets

SecretStoreInjected as
gRPC server cert + keyVault PKI → K8s Secret (Vault Agent)File mount
gRPC client cert (per caller)Vault PKIFile mount in caller pods
PostgreSQL credentialsVault DB dynamic secretEnv var (24 h TTL)
Redis credentialsVault KVEnv var
NATS credentialsVault KVEnv var
MSISDN pepperVault KVEnv var (refreshed via Vault Agent on rotation)
IMEI pepperVault KVEnv var
Per-tenant saltsVault KV (lazy fetch; cached ≤ 5 min)Runtime
MNO MNP SFTP keysVault KVFile mount
Per-MNO REST adapter client JWT credentialsVault KVRuntime (client-credentials flow)
PCAP KEKVault Transit (referenced, not exported)
Audit chain signing keyVault Transit (referenced, not exported)

No secret is ever written to logs, events, or config files. Pre-commit gitleaks scan blocks accidental commits.

8. Threat model

ThreatMitigation
Attacker scrapes Public Lookup API to enumerate MSISDNsPer-tenant quota (RPS + monthly); Kong adaptive rate-limit + JA3 fingerprint blocking; tenant-salted audit hash prevents cross-tenant correlation; anti-enumeration: uniform response time on unknown vs known MSISDN; Kong bot-detection plugin
Malicious tenant forces live HLR on every request (cost amplification)Separate freshLookupRpsLimit (default 2 req/s, lower than plan RPS); per-MNO TPS governor caps absolute SS7 traffic regardless of tenant mix
MNP file tampering (attacker modifies SFTP mid-transit)SFTP SSH host-key pinning; file SHA-256 recorded before ingest; per-MNO mutual-auth where MNO supports it; MNP run emits fileSha256 for tamper correlation across runs
MNP file replay (attacker re-ships yesterday's file with date header advanced)ReconciliationRun idempotency keyed on file_sha256; identical hash rejected with "duplicate file" error; MNO file date header cross-checked against SFTP server-side mtime
Attacker tampers with portability_history or lookup_auditAppend-only DB rules; hash-chain; daily verifier; CRITICAL alert
Compromised ni-hlr-gateway pod injects fake live attributionGateway responses signed with gateway's SPIFFE SVID; NI verifies signature against the allow-listed SPIFFE ID; pcap sampling captures the raw SS7 bytes for spot-check audit
Side-channel: reverse-lookup MSISDN from msisdn_hashPlatform pepper rotated quarterly; msisdnHash exposed only inside platform — never to tenants, citizens, or events unredacted; tenant-salted variant for audit rows
Regulator order to disclose all lookups for a specific MSISDN in the last 90 daysSupported by design: regulator uses platform.regulator role + the relevant tenant salt (held in Vault, auditable access) to re-derive the per-tenant hash; query completes ≤ 5 min
LLM prompt injection via conflict summaries (use case A)Redactor removes MSISDN/tenant IDs; LLM output constrained to JSON with grammar; output used for triage ordering only — no LLM value ever writes to numint.* tables
Denial-of-service on ni-hlr-gateway via forced-fresh spamPer-MNO Redis token bucket; per-tenant fresh-lookup sub-bucket; Kong global cap; HPA on grpc_inflight_requests
Operator malicious insider changes MnoSnapshot.hlr_endpoint to attacker-controlled hostDual-control on adapter config updates; X-Approver-User-Id required; audit; deploy-time residency test catches offshore endpoints
National-scale SIM-swap laundering via coordinated MNP abuseMNP-churn anomaly detector (see AI_INTEGRATION §3) forwards numint.mnp.churn_anomaly.v1 signals to fraud-intel-service

9. Regulatory posture

  • ATRA Numbering Plan conformance. NI implements ITU-T E.164 with Afghan country code +93; MSISDN length is validated against the ATRA-published national numbering plan (10 digits after CC); prefix-to-MNO table mirrored from ATRA assignments.
  • GSMA AA.13 MNP. MNP reconciliation patterns follow GSMA AA.13 Section 4 conventions for donor/recipient file exchange cadence and schema.
  • Regulator audit path. All MNP transitions, reconciliation runs, and conflict resolutions are hash-chained; regulator can demand verification at any time via GET /v1/regulator/numint/mnp/verify.
  • Tenant billing evidence. numint.lookup_audit is retained 13 months hot + 7 years cold; a billing dispute is resolvable within that window.
  • Lawful basis. MSISDN-to-MNO attribution is carrier-public fact; ATRA guidance places this outside the subscriber-PII envelope. Lookup API tenants attest lawful basis at account signup; the attestation is stored in auth-service.

10. Security testing

  • Contract tests (tests/contract/numint-*.spec.ts) verify every caller's defence-in-depth pattern on NI fail-degraded paths.
  • Property-based tests (tests/prop/msisdn.spec.ts) — E.164 conformance, Unicode confusables, NFKC canonicalisation, pathological inputs.
  • Property-based tests on Luhn for IMEI (3GPP TS 23.003 §6.2.1).
  • Hash-chain tamper test: mutate a payload byte; verifier MUST detect (tests/integ/chain-tamper.spec.ts).
  • Role-matrix test — every endpoint × every role — verifies 200/401/403/429 behaviour.
  • SS7 MAP fuzz tests against ni-hlr-gateway using synthetic MAP PDUs with invalid opcodes / oversized fields.
  • Residency test at deploy time.
  • ZAP baseline + API scan on each main build.
  • Quarterly penetration test scoped to Public Lookup API, admin REST, and the ni-hlr-gateway internal gRPC.
  • Secret scanning (gitleaks), dependency scan (osv-scanner), container scan (trivy).