Skip to content

Milestone: M9 — Hardening and Compliance | SOW Reference: NFR3 (cross-cutting audit) | Requirement Clarity: ✅ Clear | Dev Status: 🟡 Partially prototyped Moved from docs/requirements/tasks/POC/15-security-encryption-audit.md (audit dated 2026-04-15). Cross-checks the earlier codebase review done during today's SOW comparison — treat this as the detailed per-entity backing evidence for the summary findings in ../CONSOLIDATED-SUMMARY.md. Unmodified below.

M9-00 Task 15: Security & Encryption Audit

Overview

Comprehensive security audit mapping v3.0 requirements to current implementation. Covers data flow, encryption at rest, authentication, and compliance (GDPR, UKDIATF).

Audit Date: 2026-04-15 Source: REC Verifiable Credentialing Platform High-level Requirements v3.0 (Section: Security, Privacy and Compliance, pages 4-5)


Part 1: Data Flow Confirmation

✅ Correct Flow (Confirmed)

┌──────────────┐   1. HTTPS (TLS 1.3)   ┌──────────────┐
│   Frontend   │ ─────────────────────→ │   Backend    │
│  (Next.js)   │                        │  (NestJS)    │
│  localhost:  │ ←───────────────────── │  localhost:  │
│    3001      │   5. Response to UI    │    3000      │
└──────────────┘                        └──────┬───────┘
                                               │
                                               │ 2. HTTPS (TLS 1.3)
                                               │    Bearer Token Auth
                                               ▼
                                        ┌──────────────┐
                                        │  Velocity    │
                                        │  CIH API     │
                                        │  stagingcih. │
                                        │  velocity... │
                                        └──────┬───────┘
                                               │
                                               │ 3. Response
                                               ▼
                                        ┌──────────────┐
                                        │   Backend    │
                                        │  Processes   │
                                        │  & stores    │
                                        │  in DB       │
                                        └──────┬───────┘
                                               │
                                               │ 4. PostgreSQL
                                               ▼
                                        ┌──────────────┐
                                        │ PostgreSQL   │
                                        │ (encrypted   │
                                        │  at rest)    │
                                        └──────────────┘

Security touchpoints in this flow: 1. Frontend → Backend: TLS 1.3 + JWT (Auth0) 2. Backend → Velocity: TLS 1.3 + Bearer Token 3. Backend → Backend processing: In-memory only 4. Backend → PostgreSQL: Connection TLS + column-level encryption for PII 5. Backend → Frontend: TLS 1.3 + JWT session + @Exclude() on sensitive fields

The frontend NEVER talks to Velocity directly. This is correct — it ensures: - Bearer tokens stay on backend (never exposed to browser) - All requests audited through backend - Rate limiting enforced server-side - Webhook handling centralized


Part 2: v3.0 Security Requirements vs Current Implementation

Requirements (from v3.0 PDF, page 4)

# Requirement Current Status Gap
S1 Zero trust architecture ⚠️ Partial See details below
S2 Integrates with OAuth 2.0, SAML 2.0, OpenID Connect ✅ Done None (Auth0)
S3 Encrypt all personal data at rest (AES-256) ❌ Missing CRITICAL
S4 Encrypt all data in transit (TLS 1.3) ✅ Done None (HTTPS)
S5 Detailed immutable audit logs ⚠️ Partial Logs exist, not immutable
S6 RBAC (role-based access control) ✅ Done None
S7 UKDIATF compliance ⚠️ Unknown Need legal review
S8 GDPR compliance (data minimisation, SAR, right to erasure) ❌ Missing CRITICAL
S9 UK staffing industry data retention ❌ Missing HIGH
S10 Data segregation between companies (multi-tenancy) ✅ Done org_id scoping
S11 CCPA, SOC2 compliance ⚠️ Partial Framework in place
S12 DDoS protection ❌ Missing Deployment concern
S13 Penetration testing ❌ Not done Required pre-release

Part 3: Encryption Audit — Database Tables

What IS Already Encrypted

Entity Field Method Status
organizations velocity_tenant_keys AES-256-CBC (app-level) ⚠️ Uses deprecated createCipher API

What NEEDS Encryption (per v3.0 Section S3)

CRITICAL — Sensitive Credentials

Entity Field Why Sensitive Priority
accounts access_token, refresh_token, id_token OAuth tokens in plaintext CRITICAL
nextauth_sessions session_token Session hijacking risk CRITICAL
verification_tokens token Auth bypass risk CRITICAL

HIGH — PII Fields (GDPR)

Entity Field Why Sensitive Priority
users email, first_name, last_name, phone PII HIGH
employees email, first_name, last_name, phone PII HIGH
employees salary_amount, performance_rating Compensation data HIGH
employees supervisor_email, supervisor_name PII (third party) HIGH
employees reason_for_leaving, additional_notes Sensitive HR data HIGH
candidates first_name, last_name, email, phone PII HIGH
credential_offers payload (JSONB) Full credential = PII HIGH
email_logs recipient_email, recipient_name, variables PII in logs HIGH

MEDIUM — Supporting Data

Entity Field Why Sensitive Priority
audit_logs before, after (JSONB) Contains PII snapshots MEDIUM
kyb_records registrar_payload, evidence Corporate docs MEDIUM
verification_reports result (JSONB) Verification data MEDIUM
sessions ip, user_agent Tracking data LOW

Part 4: Immediate Action Items (POC-Critical Subset)

Not everything can be fixed before POC demo. These are the minimum security fixes to show the client good hygiene without scope creep:

Must Fix for POC (~4-6 hours)

  1. Fix deprecated crypto API in velocity-tenant.service.ts
  2. Replace createCipher() / createDecipher() (deprecated, insecure)
  3. Use createCipheriv() / createDecipheriv() with random IV
  4. Effort: 1 hour

  5. Enforce TENANT_KEYS_ENCRYPTION_KEY environment variable

  6. Remove the insecure default 'default-key-change-in-production'
  7. App should fail to start if not set in production
  8. Effort: 30 min

  9. Encrypt OAuth tokens in accounts table

  10. Add TypeORM transformer for access_token, refresh_token, id_token
  11. Use same AES-256-CBC approach as velocityTenantKeys
  12. Effort: 2 hours

  13. Mark sensitive fields with @Exclude()

  14. Prevent leaking through class-transformer in API responses
  15. Fields: tokens, raw keys, encryption metadata
  16. Effort: 30 min

  17. Add security section to demo

  18. Slide/doc showing what's encrypted, what's logged, what's audited
  19. Shows client we take security seriously
  20. Effort: 1 hour (no code)

Defer to Drop 1 Beta (Post-POC)

  1. Column-level PII encryption (employees, candidates, users)
  2. GDPR features (data export, right to erasure)
  3. Audit log immutability (append-only table, signed records)
  4. Data retention policies (auto-disposal after period)
  5. Penetration testing
  6. DDoS protection (Cloudflare, AWS Shield)

TypeORM Transformer Approach

Create a reusable encryption transformer:

// app/backend/src/shared/transformers/encrypted.transformer.ts
import { ValueTransformer } from 'typeorm';
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';

export class EncryptedColumnTransformer implements ValueTransformer {
  private readonly algorithm = 'aes-256-gcm';
  private readonly key: Buffer;

  constructor() {
    const keyHex = process.env.COLUMN_ENCRYPTION_KEY;
    if (!keyHex || keyHex.length !== 64) {
      throw new Error('COLUMN_ENCRYPTION_KEY must be 32-byte hex string');
    }
    this.key = Buffer.from(keyHex, 'hex');
  }

  to(value: string | null): string | null {
    if (value == null) return null;
    const iv = randomBytes(12);
    const cipher = createCipheriv(this.algorithm, this.key, iv);
    const ciphertext = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    return `${iv.toString('hex')}:${tag.toString('hex')}:${ciphertext.toString('hex')}`;
  }

  from(value: string | null): string | null {
    if (value == null) return null;
    const [ivHex, tagHex, ctHex] = value.split(':');
    const iv = Buffer.from(ivHex, 'hex');
    const tag = Buffer.from(tagHex, 'hex');
    const ciphertext = Buffer.from(ctHex, 'hex');
    const decipher = createDecipheriv(this.algorithm, this.key, iv);
    decipher.setAuthTag(tag);
    return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
  }
}

export const encryptedColumn = new EncryptedColumnTransformer();

Usage on Entity Fields

import { encryptedColumn } from '../../shared/transformers/encrypted.transformer';

@Entity('accounts')
export class Account {
  // ...

  @Column({ type: 'text', nullable: true, transformer: encryptedColumn })
  accessToken: string;

  @Column({ type: 'text', nullable: true, transformer: encryptedColumn })
  refreshToken: string;

  @Column({ type: 'text', nullable: true, transformer: encryptedColumn })
  idToken: string;
}

Why AES-256-GCM Over AES-256-CBC

Feature GCM (recommended) CBC (current)
Authenticated encryption ✅ Built-in auth tag ❌ Needs separate HMAC
Detects tampering ✅ Yes ❌ No
Padding attacks ✅ Immune ⚠️ Vulnerable
NIST recommended ✅ Yes Legacy only
Performance Faster Slower

Part 6: Model/Table Updates Needed

Summary Table

Action Entity Fields When
Fix organizations velocity_tenant_keys POC (fix deprecated API)
Fix N/A Remove default-key fallback POC
Add encryption accounts access_token, refresh_token, id_token POC
Add encryption nextauth_sessions session_token POC
Add @Exclude() Multiple Token fields, encryption metadata POC
Add encryption employees salary_amount, performance_rating Drop 1 Beta
Add encryption users email, phone, first_name, last_name Drop 1 Beta
Add encryption employees email, phone, name fields Drop 1 Beta
Add encryption candidates PII fields Drop 1 Beta
Add encryption credential_offers payload JSONB Drop 1 Beta
Add encryption email_logs variables JSONB Drop 1 Beta
Add encryption audit_logs before, after JSONB Drop 1 Beta
Add retention email_logs, audit_logs Auto-purge after 30-90 days Drop 1 Beta
Add immutability audit_logs Append-only, signed records Drop 2

New Environment Variables Needed

# Column-level encryption (AES-256-GCM)
# Generate with: openssl rand -hex 32
COLUMN_ENCRYPTION_KEY=<32-byte hex string, REQUIRED in production>

# Tenant keys encryption (existing, but enforce)
TENANT_KEYS_ENCRYPTION_KEY=<32-byte hex string, REQUIRED in production>

# Data retention policies
AUDIT_LOG_RETENTION_DAYS=2555  # 7 years for UK staffing compliance
EMAIL_LOG_RETENTION_DAYS=90
SESSION_RETENTION_DAYS=30

Part 7: Compliance Checklist for Client

✅ What We Can Show the Client NOW

  • Multi-factor authentication (Auth0 MFA) — S2
  • OAuth 2.0 / OpenID Connect integration — S2
  • TLS 1.3 in transit — S4
  • Role-based access control (Platform Admin, Org Admin, Org User) — S6
  • Multi-tenant data segregation (every table has org_id) — S10
  • Session management with expiration — S6
  • Audit logging (every action tracked) — S5 (partial)
  • Velocity tenant keys encrypted at rest — S3 (partial)

⚠️ What We'll Address in POC (~6 hours)

  • Fix deprecated crypto API → use modern AES-256-GCM
  • Encrypt OAuth tokens in accounts table
  • Enforce required encryption keys (no insecure defaults)
  • Mark sensitive fields @Exclude() in API responses

⏳ What's Planned for Drop 1 Beta

  • Full PII encryption on employees, candidates, users, credentials
  • Encrypted audit log state snapshots
  • Data retention policies

⏳ What's Planned for Drop 2 / Production

  • GDPR features (SAR, right to erasure)
  • Audit log immutability with cryptographic signatures
  • Penetration testing (third-party)
  • DDoS protection (Cloudflare / AWS Shield)
  • WAF rules
  • SOC 2 compliance audit

Part 8: Security Talking Points for Client Demo

If security is raised in the demo, these are prepared answers:

Q: "How do you encrypt data?" A: "Data in transit is TLS 1.3. Data at rest uses AES-256-GCM column-level encryption for tokens, keys, and PII. The database itself runs on encrypted PostgreSQL instances (disk-level)."

Q: "What about GDPR?" A: "Architecture supports GDPR: every record has org_id scoping, audit logs track all access, soft-delete with deleted_at enables right-to-erasure, and we'll implement data subject access requests in Drop 1 Beta before production."

Q: "Who can access what?" A: "Role-based access control with 4 levels: Platform Admin, Org Admin, Org User, Public (claim pages). Every API endpoint is guarded by role + organization scoping. All requests audited."

Q: "What if the database is compromised?" A: "Attacker would need: (1) TLS private keys for connection, (2) TENANT_KEYS_ENCRYPTION_KEY + COLUMN_ENCRYPTION_KEY from env vars (stored separately), (3) Auth0 access. Defense in depth."

Q: "How do you handle credentials (passwords)?" A: "We don't store passwords. Authentication is delegated to Auth0 with MFA. We only store Auth0 IDs. Optional passkey support (WebAuthn) is in the architecture for Drop 1."

Q: "What about the Velocity bearer token? Isn't that a secret?" A: "Yes — it's stored in backend environment variables only, never exposed to frontend. All Velocity API calls happen server-side. The token has limited scope (CAO operations only) and rotation can be requested from Velocity."


Part 9: Next Steps

For POC (minimum security hygiene)

  1. Create EncryptedColumnTransformer using AES-256-GCM
  2. Fix velocity-tenant.service.ts deprecated API
  3. Remove insecure default encryption key
  4. Add COLUMN_ENCRYPTION_KEY to .env
  5. Apply transformer to accounts.access_token, refresh_token, id_token
  6. Apply transformer to nextauth_sessions.session_token
  7. Mark token fields with @Exclude()
  8. Document security posture (this file + client talking points)

For Drop 1 Beta

  1. Apply encryption to all PII fields (users, employees, candidates)
  2. Encrypt credential offer payloads
  3. Encrypt audit log state snapshots
  4. Implement data retention policies
  5. Add GDPR data export endpoint
  6. Add right-to-erasure workflow

For Drop 2 / Production

  1. Third-party penetration testing
  2. DDoS protection
  3. WAF configuration
  4. SOC 2 audit
  5. UKDIATF compliance review (legal)
  6. Audit log signing / immutability
  7. Key rotation automation
  8. HSM consideration for key management

References

  • v3.0 Requirements PDF (Security, Privacy and Compliance section, pages 4-5)
  • Task 10: Gap Analysis — Non-functional requirements tracking
  • Task 14: POC Readiness Audit — Overall readiness
  • Node.js Crypto docs: https://nodejs.org/api/crypto.html (for AES-256-GCM)
  • NIST SP 800-38D (Authenticated Encryption)
  • GDPR Article 32 (Security of Processing)