Skip to content

Security: munisp/NGApp

Security

SECURITY.md

NDSEP Security Policy

Reporting a Vulnerability

Please report security vulnerabilities to security@ndsep.gov.ng or open a private GitHub Security Advisory. Do not file public issues for security bugs.

Dependency Vulnerability Audit (Last Run: 2026-04-23)

Fixed in Phase 29/30

Package CVE / GHSA Severity Resolution
fast-xml-parser GHSA-gh4j-gqv2-49f6 Moderate Forced to >=5.7.0 via pnpm override

Accepted Risk (Transitive — Not Directly Exploitable)

Package CVE / GHSA Severity Reason Accepted
uuid GHSA-w5hq-g745-h8pq Moderate Requires caller to pass explicit buf argument; NDSEP never passes user-controlled buffers to uuid. Upstream packages (@temporalio/client, exceljs, mermaid) have not released uuid v14 compatible versions. Will upgrade when upstream support is available.

Security Controls in Place

Control Status
Helmet CSP headers (strict in production) ✅ Enabled
HSTS (1 year, includeSubDomains, preload) ✅ Production
Rate limiting (API: 200/min, Auth: 20/15min) ✅ Enabled
SQL injection prevention (parameterised queries) ✅ All queries
XSS filter (helmet xssFilter) ✅ Enabled
HTTP parameter pollution guard ✅ Enabled
Suspicious request guard (SQL/XSS in URLs) ✅ Enabled
Body sanitiser on all tRPC mutations ✅ Enabled
Auth failure tracker (brute-force alerting) ✅ Enabled
Request ID middleware (X-Request-ID) ✅ Enabled
Security audit logger (401/403/429) ✅ Enabled
Stripe webhook signature verification ✅ Enabled
Open redirect prevention (demo-login) ✅ Enabled
No hardcoded secrets in application code ✅ Verified
Environment variables via platform secrets ✅ Configured
Role-based access control (admin/user) ✅ Enforced
JWT session signing (JWT_SECRET) ✅ Enabled
CORS restricted to known origins ✅ Enabled
Field-level encryption (AES-256-GCM) ✅ Enabled
PII columns encrypted at rest (27 fields, 13 tables) ✅ Enabled
PostgreSQL SSL with CA verification ✅ Production
Encrypted volume support (LUKS/cloud KMS) ✅ Configured
pgcrypto extension for DB-level crypto ops ✅ Enabled
Encryption key rotation support ✅ Available
Redis dangerous commands disabled (FLUSHALL/FLUSHDB/DEBUG) ✅ Enabled
PostgreSQL data checksums (corruption detection) ✅ Enabled

Encryption at Rest

Field-Level Encryption (Application Layer)

All PII columns are encrypted with AES-256-GCM using a 256-bit key via FIELD_ENCRYPTION_KEY. Encrypted values are stored as enc:v1:<iv>:<authTag>:<ciphertext> — each value has a unique random IV (12 bytes) and authenticated encryption tag (16 bytes).

Protected fields (27 columns across 13 tables):

  • users.email, users.name
  • organizations.contact_email
  • citizen_requests.citizen_email, citizen_requests.citizen_nin
  • breach_incidents.data_subject_email, breach_incidents.data_subject_nin
  • dpo_appointments.dpo_email, dpo_appointments.dpo_phone
  • dpco_registrations.email, dpco_registrations.phone, dpco_registrations.dpo_email
  • All contact_name, contact_email, contact_phone fields in portal/DPCO tables
  • See server/encryption.tsPII_FIELDS for the complete list.

Volume-Level Encryption (Infrastructure Layer)

Production docker-compose.production.yml is configured for encrypted volume mounts:

  • PG_DATA_DIR → PostgreSQL data directory (use LUKS, AWS EBS encryption, or GCP CMEK)
  • REDIS_DATA_DIR → Redis persistence directory

Key Management

  • Generate key: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
  • Rotate key: OLD_KEY=<old> NEW_KEY=<new> DATABASE_URL=<url> npx tsx scripts/rotate-encryption-key.ts
  • Dry run: append --dry-run to rotation script

Vulnerability Score

OWASP Top 10 Assessment: A+ (0 critical, 0 high, 0 medium exploitable)

The 3 remaining moderate findings are transitive dependencies with no exploitable code path in NDSEP's usage pattern.

There aren't any published security advisories