Volume 2: Organizational Intelligence Platforms

Pattern 31: Privacy Architecture

Intent

Build privacy protection into system architecture from day one through encryption, access controls, audit logging, data minimization, and compliance with privacy regulations (GDPR, HIPAA, CCPA), creating systems that defend user data against unauthorized access, breaches, and misuse while maintaining transparency and user control.

Also Known As

  • Privacy by Design
  • Secure Architecture
  • Data Protection Architecture
  • Compliance-First Design
  • Defense in Depth

Problem

Systems handling personal data face constant threats from crooks, creeps, hackers, and insider abuse. Privacy violations destroy trust and expose organizations to massive legal and financial consequences.

The "afterthought security" disaster:

Developer builds system first, adds security later:

// Version 1.0: Just make it work!
app.post('/family/create', (req, res) => {
  db.query(`
    INSERT INTO families (
      name, email, phone, address,
      ssn, credit_card, bank_account
    ) VALUES (?, ?, ?, ?, ?, ?, ?)
  `, [req.body.name, req.body.email, ...]);

  res.json({success: true});
});

// Stored in database:
// - SSN: 123-45-6789 (PLAIN TEXT!)
// - Credit card: 4532-1234-5678-9010 (PLAIN TEXT!)
// - No encryption, no access controls, no audit log
// DISASTER WAITING TO HAPPEN! 😱

Six months later: Data breach - Hacker gets database dump - All SSNs, credit cards exposed in plain text - 10,000 families' data stolen - $50 million lawsuit + destroyed reputation 💀

The problem: Security was afterthought, not foundation!

The "crooks and creeps" threat model:

External threats (crooks): - Hackers trying SQL injection - Ransomware attackers - Credit card thieves - Identity theft rings - State-sponsored attackers

Internal threats (creeps): - Nosy employees browsing data - Admins abusing access - Contractors stealing info - Disgruntled ex-employees - Insider trading on private data

Accidental threats: - Database misconfigured (public access!) - Backup left on public S3 bucket - Logs containing passwords - Email sent to wrong person - Laptop stolen with unencrypted data

ALL threats are real! Systems must defend against ALL! 🛡️

The compliance nightmare:

CEO: "We need to comply with GDPR."

Developer: "What's GDPR?"

CEO: "European privacy law. Users can request their data, request deletion, opt out of tracking..."

Developer: "Uh... we store data in 50 tables across 5 databases with no indexes on user_id. Deletion would require manual SQL in production. We don't track what data belongs to whom. We have backups going back 7 years. This will take 6 months to fix!"

CEO: "We launch in EU next month!"

Developer: "We're screwed." 😱

The problem: Privacy not built in from start!

The sensitive data exposure:

Homeschool co-op stores: - Family names, addresses, phone - Parent SSNs (for tax purposes) - Children's birthdates, medical info - Credit card numbers - Bank account info (ACH payments) - Email addresses - User passwords

Single database breach exposes: - Identity theft (SSN + address) - Financial fraud (credit cards) - Child predator targeting (children's names, ages, addresses) - Account takeover (passwords)

The damage: Families' lives RUINED by one breach! 😱💔

The insider abuse:

Mike (coordinator) has database access for his job.

Mike's friend asks: "Is Johnson family enrolled?"

Mike queries database: SELECT * FROM families WHERE name = 'Johnson'

Gets: Johnson family's address, phone, children's names, payment history

Tells friend: "Yeah, they're enrolled. Live at 123 Oak St, have 3 kids..."

Privacy violation! No audit trail, no access control, no oversight! 😱

The accidental exposure:

Developer takes production database dump to test locally:

pg_dump production_db > backup.sql
# File contains ALL user data in PLAIN TEXT!

Developer's laptop stolen from coffee shop.

Backup file includes: - 10,000 families' data - SSNs, credit cards, addresses - All in plain text SQL!

Massive breach from simple mistake! 😱

What we need: Privacy Architecture

Defense in depth:

Layer 1: Encryption (data unreadable if stolen)
Layer 2: Access controls (only authorized access)
Layer 3: Audit logging (track all access)
Layer 4: Data minimization (don't collect what you don't need)
Layer 5: Anonymization (can't identify individuals)
Layer 6: Compliance (legal requirements met)

If one layer fails, others still protect! 🛡️

Privacy by design principles:

1. Encrypt everything - At rest (database, backups, files) - In transit (HTTPS, TLS) - In use (application-level encryption)

2. Minimize data collection - Don't ask for SSN if not needed - Don't store credit cards (use tokenization) - Delete data when no longer needed

3. Control access - Role-based access control (RBAC) - Least privilege principle - Multi-factor authentication (MFA)

4. Audit everything - Log all data access - Track who accessed what when - Alert on suspicious patterns

5. Enable user rights - Users can view their data - Users can export their data - Users can delete their data

6. Design for breach - Assume breach will happen - Minimize damage when it does - Detect breaches quickly

Without privacy architecture: - Plain text storage (instant breach damage) - No access controls (anyone can see anything) - No audit trail (abuse undetected) - Compliance violations (lawsuits, fines) - User trust destroyed (business failure)

With privacy architecture: - Encrypted storage (stolen data useless) - Strict access controls (least privilege) - Complete audit trail (abuse detected) - Compliance built-in (legal safety) - User trust maintained (business success)

Context

When this pattern applies:

  • Handling personal data (names, emails, addresses)
  • Handling sensitive data (SSN, credit cards, medical)
  • Handling children's data (COPPA requirements)
  • Subject to regulations (GDPR, HIPAA, CCPA)
  • High-value target (financial, healthcare, education)
  • Need user trust (competitive advantage)

When this pattern may not be needed:

  • Public data only (already public)
  • Completely anonymous (no personal data)
  • Temporary/disposable data

Forces

Competing concerns:

1. Privacy vs Convenience - High privacy = more friction (MFA, encryption) - High convenience = less privacy (auto-login, plain text) - Balance: Privacy by default, convenience optional

2. Security vs Performance - Encryption = CPU overhead - Access checks = latency - Balance: Acceptable overhead for protection

3. Compliance vs Agility - Compliance = strict controls - Agility = move fast - Balance: Built-in compliance, fast iteration

4. Data Utility vs Privacy - More data = better features - Less data = better privacy - Balance: Collect minimum needed

5. Access vs Control - Open access = productivity - Restricted access = security - Balance: Least privilege + audit

Solution

Build privacy-first architecture with:

1. Encryption Everywhere

At Rest (Database):

const crypto = require('crypto');

class EncryptionService {
  constructor() {
    // Encryption key from environment (NEVER hardcode!)
    this.key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
    this.algorithm = 'aes-256-gcm';
  }

  encrypt(plaintext) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);

    let encrypted = cipher.update(plaintext, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    const authTag = cipher.getAuthTag();

    // Return: iv + authTag + encrypted (all needed for decryption)
    return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
  }

  decrypt(ciphertext) {
    const parts = ciphertext.split(':');
    const iv = Buffer.from(parts[0], 'hex');
    const authTag = Buffer.from(parts[1], 'hex');
    const encrypted = parts[2];

    const decipher = crypto.createDecipheriv(this.algorithm, this.key, iv);
    decipher.setAuthTag(authTag);

    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }
}

const encryption = new EncryptionService();

// Store encrypted
await db.query(`
  INSERT INTO families (name, ssn_encrypted, credit_card_encrypted)
  VALUES (?, ?, ?)
`, [
  name,
  encryption.encrypt(ssn),
  encryption.encrypt(creditCard)
]);

// Retrieve and decrypt
const result = await db.query('SELECT * FROM families WHERE id = ?', [id]);
const family = {
  name: result[0].name,
  ssn: encryption.decrypt(result[0].ssn_encrypted),
  creditCard: encryption.decrypt(result[0].credit_card_encrypted)
};

In Transit (HTTPS):

// Force HTTPS
app.use((req, res, next) => {
  if (!req.secure && process.env.NODE_ENV === 'production') {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
});

// Strict Transport Security (HSTS)
app.use((req, res, next) => {
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  next();
});

2. Access Control (RBAC)

Role-based permissions:

// Define roles and permissions
const ROLES = {
  PARENT: {
    permissions: [
      'view:own_family',
      'edit:own_family',
      'view:own_payments'
    ]
  },
  COORDINATOR: {
    permissions: [
      'view:all_families',
      'edit:all_families',
      'view:all_payments',
      'send:communications'
    ]
  },
  ADMIN: {
    permissions: [
      '*'  // All permissions
    ]
  }
};

// Middleware to check permissions
function requirePermission(permission) {
  return async (req, res, next) => {
    const user = req.user;
    const userRole = ROLES[user.role];

    // Check if user has permission
    if (userRole.permissions.includes('*') || 
        userRole.permissions.includes(permission)) {
      return next();
    }

    // Log unauthorized access attempt
    await auditLog.record({
      user_id: user.id,
      action: 'UNAUTHORIZED_ACCESS_ATTEMPT',
      permission: permission,
      ip: req.ip,
      timestamp: new Date()
    });

    return res.status(403).json({error: 'Forbidden'});
  };
}

// Protected routes
app.get('/api/families', 
  requirePermission('view:all_families'),
  async (req, res) => {
    // Only coordinators and admins reach here
    const families = await db.query('SELECT * FROM families');
    res.json(families);
  }
);

app.get('/api/family/:id',
  async (req, res) => {
    const familyId = req.params.id;
    const user = req.user;

    // Parents can only view own family
    if (user.role === 'PARENT' && user.family_id !== familyId) {
      await auditLog.record({
        user_id: user.id,
        action: 'UNAUTHORIZED_ACCESS_ATTEMPT',
        resource: `family:${familyId}`,
        ip: req.ip
      });

      return res.status(403).json({error: 'Forbidden'});
    }

    const family = await db.query('SELECT * FROM families WHERE id = ?', [familyId]);
    res.json(family);
  }
);

3. Comprehensive Audit Logging

Track all data access:

class AuditLog {
  constructor(db) {
    this.db = db;
  }

  async record(event) {
    await this.db.query(`
      INSERT INTO audit_log (
        user_id,
        action,
        resource_type,
        resource_id,
        details,
        ip_address,
        user_agent,
        timestamp
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `, [
      event.user_id,
      event.action,
      event.resource_type,
      event.resource_id,
      JSON.stringify(event.details || {}),
      event.ip,
      event.user_agent,
      event.timestamp || new Date()
    ]);
  }

  async getAccessHistory(userId) {
    return await this.db.query(`
      SELECT * FROM audit_log
      WHERE user_id = ?
      ORDER BY timestamp DESC
      LIMIT 100
    `, [userId]);
  }

  async detectSuspiciousActivity() {
    // Alert on unusual patterns
    const suspicious = await this.db.query(`
      SELECT user_id, COUNT(*) as attempts
      FROM audit_log
      WHERE action = 'UNAUTHORIZED_ACCESS_ATTEMPT'
        AND timestamp > NOW() - INTERVAL 1 HOUR
      GROUP BY user_id
      HAVING attempts > 5
    `);

    for (const user of suspicious) {
      await this.alertSecurity({
        user_id: user.user_id,
        reason: `${user.attempts} unauthorized access attempts in 1 hour`,
        severity: 'HIGH'
      });
    }
  }
}

// Log all sensitive data access
app.get('/api/family/:id/ssn', 
  requirePermission('view:sensitive_data'),
  async (req, res) => {
    await auditLog.record({
      user_id: req.user.id,
      action: 'VIEW_SSN',
      resource_type: 'family',
      resource_id: req.params.id,
      ip: req.ip,
      user_agent: req.headers['user-agent']
    });

    const family = await db.query('SELECT ssn_encrypted FROM families WHERE id = ?', [req.params.id]);
    res.json({ssn: encryption.decrypt(family[0].ssn_encrypted)});
  }
);

4. Data Minimization

Don't collect what you don't need:

// BAD: Collecting unnecessary data
app.post('/api/signup', async (req, res) => {
  await db.query(`
    INSERT INTO users (
      name, email, password,
      ssn,            -- Why do we need this?
      birth_date,     -- Why do we need this?
      mother_maiden,  -- Security question? NO!
      credit_card     -- Store tokenized only!
    ) VALUES (...)
  `);
});

// GOOD: Collect only what's needed
app.post('/api/signup', async (req, res) => {
  // Validate: Collect minimum
  const required = ['name', 'email', 'password'];
  for (const field of required) {
    if (!req.body[field]) {
      return res.status(400).json({error: `${field} required`});
    }
  }

  await db.query(`
    INSERT INTO users (name, email, password_hash)
    VALUES (?, ?, ?)
  `, [
    req.body.name,
    req.body.email,
    await bcrypt.hash(req.body.password, 10)
  ]);

  // Don't store: SSN, birth_date, credit_card, security questions
});

5. Tokenization (Don't Store Credit Cards)

Use payment processor tokens:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// NEVER store credit card directly!
app.post('/api/payment-method', async (req, res) => {
  // Create Stripe token (Stripe stores card, gives us token)
  const paymentMethod = await stripe.paymentMethods.create({
    type: 'card',
    card: {
      number: req.body.card_number,
      exp_month: req.body.exp_month,
      exp_year: req.body.exp_year,
      cvc: req.body.cvc
    }
  });

  // Store ONLY the token (not the card!)
  await db.query(`
    INSERT INTO payment_methods (
      family_id,
      stripe_payment_method_id,  -- Token, not card!
      last_four,                  -- For display only
      brand
    ) VALUES (?, ?, ?, ?)
  `, [
    req.user.family_id,
    paymentMethod.id,           // pm_abc123...
    req.body.card_number.slice(-4),
    'visa'
  ]);

  // If database breached: Tokens useless to attacker!
  // Actual cards: Safe at Stripe (PCI compliant)
});

6. Anonymization & Pseudonymization

For analytics, use anonymized data:

// Create anonymized analytics view
async function createAnalyticsView() {
  await db.query(`
    CREATE VIEW analytics_enrollments AS
    SELECT 
      DATE_TRUNC('day', enrollment_date) as enrollment_day,
      LEFT(zip_code, 3) as zip_prefix,  -- First 3 digits only
      age_group,                         -- "30-40", not exact age
      COUNT(*) as count
    FROM families
    GROUP BY enrollment_day, zip_prefix, age_group
  `);
}

// Analytics query: No PII exposed
const stats = await db.query(`
  SELECT * FROM analytics_enrollments
  WHERE enrollment_day >= '2024-01-01'
`);
// Returns aggregated data only, no individual identification

7. Data Retention & Deletion

Auto-delete old data:

// Data retention policy
class DataRetention {
  constructor(db) {
    this.db = db;
    this.policies = {
      'audit_log': '7 years',      // Legal requirement
      'sessions': '30 days',       // Security
      'temp_files': '7 days',      // Cleanup
      'withdrawn_families': '1 year'  // Business need
    };
  }

  async enforceRetention() {
    // Delete old audit logs
    await this.db.query(`
      DELETE FROM audit_log
      WHERE timestamp < NOW() - INTERVAL '7 years'
    `);

    // Delete old sessions
    await this.db.query(`
      DELETE FROM sessions
      WHERE created_at < NOW() - INTERVAL '30 days'
    `);

    // Delete withdrawn families (after 1 year)
    await this.db.query(`
      DELETE FROM families
      WHERE status = 'withdrawn'
        AND withdrawal_date < NOW() - INTERVAL '1 year'
    `);

    console.log('Data retention policy enforced');
  }
}

// Run nightly
cron.schedule('0 2 * * *', async () => {
  const retention = new DataRetention(db);
  await retention.enforceRetention();
});

8. GDPR Compliance: User Rights

Right to access:

app.get('/api/my-data', async (req, res) => {
  const userId = req.user.id;

  // Gather ALL data for this user
  const data = {
    profile: await db.query('SELECT * FROM users WHERE id = ?', [userId]),
    family: await db.query('SELECT * FROM families WHERE user_id = ?', [userId]),
    payments: await db.query('SELECT * FROM payments WHERE user_id = ?', [userId]),
    communications: await db.query('SELECT * FROM messages WHERE user_id = ?', [userId]),
    audit_log: await db.query('SELECT * FROM audit_log WHERE user_id = ?', [userId])
  };

  res.json(data);

  // Log access
  await auditLog.record({
    user_id: userId,
    action: 'DATA_EXPORT',
    timestamp: new Date()
  });
});

Right to deletion:

app.delete('/api/my-account', async (req, res) => {
  const userId = req.user.id;

  // Verify user intent (require password)
  const valid = await bcrypt.compare(req.body.password, req.user.password_hash);
  if (!valid) {
    return res.status(401).json({error: 'Invalid password'});
  }

  // Delete user data (GDPR: "Right to be forgotten")
  await db.query('DELETE FROM messages WHERE user_id = ?', [userId]);
  await db.query('DELETE FROM payments WHERE user_id = ?', [userId]);
  await db.query('DELETE FROM families WHERE user_id = ?', [userId]);
  await db.query('DELETE FROM users WHERE id = ?', [userId]);

  // Anonymize audit log (can't delete for compliance)
  await db.query(`
    UPDATE audit_log
    SET user_id = NULL, details = 'DELETED_USER'
    WHERE user_id = ?
  `, [userId]);

  res.json({success: true, message: 'Account deleted'});

  // Log deletion
  await auditLog.record({
    user_id: 'DELETED',
    action: 'ACCOUNT_DELETED',
    details: {original_user_id: userId},
    timestamp: new Date()
  });
});

Structure

Privacy Architecture Tables

-- Encrypted sensitive data
CREATE TABLE families (
  family_id INT PRIMARY KEY,

  -- Public data (not encrypted)
  family_name VARCHAR(200),
  enrollment_date DATE,
  status VARCHAR(50),

  -- Sensitive data (ENCRYPTED!)
  ssn_encrypted TEXT,              -- AES-256-GCM encrypted
  credit_card_token VARCHAR(200),  -- Stripe token, not actual card
  bank_account_encrypted TEXT,     -- Encrypted

  -- Non-sensitive computed
  children_count INT,

  created_at DATETIME2 DEFAULT GETDATE(),
  updated_at DATETIME2
);

-- Audit log (immutable, never delete except per retention policy)
CREATE TABLE audit_log (
  log_id BIGINT PRIMARY KEY IDENTITY(1,1),

  user_id VARCHAR(200),  -- Nullable (deleted users)
  action VARCHAR(200) NOT NULL,

  resource_type VARCHAR(100),
  resource_id VARCHAR(200),

  details NVARCHAR(MAX),  -- JSON

  ip_address VARCHAR(45),
  user_agent VARCHAR(500),

  timestamp DATETIME2 DEFAULT GETDATE(),

  -- Immutable (can't update or delete!)
  CONSTRAINT CHK_immutable CHECK (log_id > 0)
);

-- Index for suspicious activity detection
CREATE INDEX IX_audit_unauthorized ON audit_log(action, timestamp) 
WHERE action = 'UNAUTHORIZED_ACCESS_ATTEMPT';

-- User roles and permissions
CREATE TABLE user_roles (
  user_id VARCHAR(200) PRIMARY KEY,
  role VARCHAR(50) NOT NULL,  -- 'PARENT', 'COORDINATOR', 'ADMIN'

  assigned_by VARCHAR(200),
  assigned_at DATETIME2 DEFAULT GETDATE(),

  CONSTRAINT FK_user_roles_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- Data access permissions (fine-grained)
CREATE TABLE data_permissions (
  permission_id INT PRIMARY KEY IDENTITY(1,1),

  user_id VARCHAR(200),
  resource_type VARCHAR(100),  -- 'family', 'payment', 'child'
  resource_id VARCHAR(200),

  permission VARCHAR(50),  -- 'read', 'write', 'delete'

  granted_by VARCHAR(200),
  granted_at DATETIME2 DEFAULT GETDATE(),
  expires_at DATETIME2,  -- Optional expiration

  CONSTRAINT FK_permissions_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);

-- Data deletion requests (GDPR)
CREATE TABLE deletion_requests (
  request_id INT PRIMARY KEY IDENTITY(1,1),

  user_id VARCHAR(200),
  requested_at DATETIME2 DEFAULT GETDATE(),

  status VARCHAR(50) DEFAULT 'pending',  -- 'pending', 'processing', 'complete'

  completed_at DATETIME2,
  deleted_records INT,

  CONSTRAINT FK_deletion_user FOREIGN KEY (user_id) REFERENCES users(user_id)
);

Implementation

[Code examples above cover implementation - this section would include additional patterns like MFA, password policies, session management, etc.]

Variations

By Encryption Strategy

Application-Level: - Encrypt/decrypt in application code - Fine-grained control - Requires key management

Database-Level: - Transparent Data Encryption (TDE) - Database handles encryption - Easier but less granular

Hybrid: - TDE for bulk encryption - Application for sensitive fields - Best of both

By Compliance Level

Basic: - Encryption, access controls - Good for low-risk data

HIPAA: - Healthcare data - Strict audit requirements - Business Associate Agreements

PCI-DSS: - Payment card data - Tokenization required - Never store CVV

GDPR: - EU personal data - User rights (access, deletion) - Data protection officer

Consequences

Benefits

1. Breach protection Encrypted data useless if stolen.

2. Compliance GDPR, HIPAA, CCPA requirements met.

3. User trust Privacy protection = competitive advantage.

4. Insider protection Access controls + audit prevent abuse.

5. Legal safety Defensible in court/audits.

Costs

1. Performance overhead Encryption/decryption takes CPU.

2. Complexity More code, more concepts.

3. Key management Encryption keys must be secured.

4. Storage overhead Audit logs, encrypted data larger.

5. Development friction Privacy checks slow development slightly.

Sample Code

Complete privacy-first API:

[Code examples above demonstrate this]

Known Uses

Healthcare (HIPAA) - Full encryption at rest - Detailed audit logging - Role-based access - Patient data rights

Financial (PCI-DSS) - No credit card storage - Tokenization only - Transaction audit trail - Fraud detection

Government - Classified data encryption - Need-to-know access - Complete audit trails - Data retention policies

Stripe - PCI compliant - Tokenization - Audit everything - User data encrypted

Requires: - Pattern 27: Event Sourcing - audit trail - Pattern 26: Feedback Loop - anomaly detection

Enables: - Regulatory compliance - User trust - Legal protection

References

Regulatory Frameworks

Security Standards

Privacy by Design

Encryption & Data Protection

Data Minimization & Anonymization

  • Pattern 5: Privacy Preserving Observation - Behavioral tracking with privacy
  • Pattern 18: Audit Trail - Privacy-compliant audit logging
  • Pattern 31: Privacy Architecture - System-wide privacy design
  • Volume 3, Pattern 1: Progressive Disclosure - Collect minimal data
  • Volume 3, Pattern 18: Audit Trail - Track data access

Practical Implementation

Privacy Management Tools