tractatus/docs/plans/security-implementation-roadmap.md
TheFlow 2298d36bed fix(submissions): restructure Economist package and fix article display
- Create Economist SubmissionTracking package correctly:
  * mainArticle = full blog post content
  * coverLetter = 216-word SIR— letter
  * Links to blog post via blogPostId
- Archive 'Letter to The Economist' from blog posts (it's the cover letter)
- Fix date display on article cards (use published_at)
- Target publication already displaying via blue badge

Database changes:
- Make blogPostId optional in SubmissionTracking model
- Economist package ID: 68fa85ae49d4900e7f2ecd83
- Le Monde package ID: 68fa2abd2e6acd5691932150

Next: Enhanced modal with tabs, validation, export

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 08:47:42 +13:00

97 KiB
Raw Permalink Blame History

Security Implementation Roadmap

Tractatus Comprehensive Security Framework

Version: 1.0 Created: 2025-10-14 Status: Implementation Plan Implements: inst_041, inst_042, inst_043, inst_044, inst_045, inst_046


Executive Summary

This roadmap implements a 6-layer defense-in-depth security framework for Tractatus using exclusively sovereign tools and trusted communication platforms (Proton, Signal). The implementation is structured in 6 phases over approximately 8-12 weeks, with each phase building on the previous while remaining independently testable.

Security Layers:

  1. File Upload Validation (inst_041)
  2. Email Security Pipeline (inst_042)
  3. Form Input Sanitization (inst_043)
  4. HTTP Security Headers (inst_044)
  5. API Endpoint Protection (inst_045)
  6. Security Monitoring & Alerting (inst_046)

Core Principles:

  • Sovereign tools only: Open-source, auditable, self-hosted
  • Defense in depth: Multiple overlapping security layers
  • Zero external dependencies: No cloud services or proprietary APIs
  • Comprehensive logging: All security events logged and monitored
  • Fail-safe defaults: Reject suspicious inputs, never auto-process

Communication Infrastructure

Proton Suite

  • ProtonMail: Secure email for security alerts, weekly reports, incident notifications
  • ProtonVPN: Secure access to production servers during deployment
  • ProtonDrive: Encrypted storage for security documentation, incident reports, backup logs

Setup Requirements:

  • Proton Business or Visionary account
  • Custom domain integration for security@tractatus.digital, admin@tractatus.digital
  • ProtonMail Bridge for local email client integration (if needed)

Signal

  • Text Messaging: Critical security alerts, incident coordination
  • Video Conferencing: Security reviews, incident response, team coordination
  • Setup: Security team group, escalation protocols, mobile device requirements

Prerequisites

Infrastructure Requirements

  • Production Server: Ubuntu 22.04 LTS (vps-93a693da.vps.ovh.net)
  • Development Server: Local Ubuntu environment
  • Access: SSH keys, sudo privileges, systemd management
  • Database: MongoDB 5.0+ already installed (port 27017)
  • Web Server: nginx (reverse proxy for Node.js app on port 9000)
  • Node.js: v18+ with npm
  • Redis: Required for distributed rate limiting (Phase 4)

Team Requirements

  • System Administrator: Server configuration, tool installation, fail2ban setup
  • Developer: Middleware implementation, API integration, testing
  • Security Reviewer: Configuration validation, penetration testing, audit
  • Project Owner: Approval of security policies, alert thresholds, incident protocols

Documentation

  • Read: inst_041 through inst_046 in .claude/instruction-history.json
  • Review: CLAUDE_Tractatus_Maintenance_Guide.md sections on security
  • Prepare: Incident response playbook (template provided in Phase 6)

Phase 1: Foundation & Sovereign Tools Installation

Duration: 1-2 weeks Effort: 20-30 hours Dependencies: None Risk: Low

Objectives

  1. Install and configure all sovereign security tools
  2. Set up base logging infrastructure
  3. Establish secure communication channels
  4. Create security documentation structure

Tasks

1.1 ClamAV Antivirus Installation

# Install ClamAV
sudo apt update
sudo apt install -y clamav clamav-daemon clamav-freshclam

# Stop services to update database
sudo systemctl stop clamav-freshclam
sudo systemctl stop clamav-daemon

# Manual database update (first time)
sudo freshclam

# Start services
sudo systemctl start clamav-freshclam
sudo systemctl start clamav-daemon

# Enable auto-start
sudo systemctl enable clamav-freshclam
sudo systemctl enable clamav-daemon

# Verify installation
clamscan --version
clamdscan --version

Configuration:

  • Edit /etc/clamav/clamd.conf:

    • MaxFileSize 50M (matches inst_041 media limit)
    • MaxScanSize 100M
    • StreamMaxLength 50M
    • LogFile /var/log/clamav/clamav.log
    • LogTime yes
    • LogFileMaxSize 10M
  • Edit /etc/clamav/freshclam.conf:

    • UpdateLogFile /var/log/clamav/freshclam.log
    • DatabaseMirror database.clamav.net
    • Checks 24 (daily updates as per inst_041)

Testing:

# Test with EICAR test file
wget http://www.eicar.org/download/eicar.com.txt
clamscan eicar.com.txt  # Should detect as malware
rm eicar.com.txt

# Test daemon scanning
echo "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" > test.txt
clamdscan test.txt  # Should detect malware
rm test.txt

1.2 YARA Installation & Rule Configuration

# Install YARA
sudo apt install -y yara

# Verify installation
yara --version

Create YARA Rules Directory:

sudo mkdir -p /etc/yara/rules
sudo mkdir -p /var/log/yara

Create Base Rule Set (/etc/yara/rules/tractatus-base.yar):

rule Suspicious_Executable_in_Document {
    meta:
        description = "Detects embedded executables in document files"
        severity = "high"
    strings:
        $mz = "MZ"
        $pe = "PE"
    condition:
        uint16(0) == 0x5A4D and $pe in (0..1024)
}

rule Suspicious_Script_Injection {
    meta:
        description = "Detects potential script injection patterns"
        severity = "high"
    strings:
        $js1 = "<script" nocase
        $js2 = "javascript:" nocase
        $js3 = "eval(" nocase
        $php1 = "<?php" nocase
        $cmd1 = "system(" nocase
        $cmd2 = "exec(" nocase
    condition:
        2 of them
}

rule Suspicious_Macro_Indicators {
    meta:
        description = "Detects suspicious macro indicators in Office documents"
        severity = "medium"
    strings:
        $auto1 = "AutoOpen" nocase
        $auto2 = "AutoExec" nocase
        $auto3 = "Workbook_Open" nocase
        $cmd = "Shell" nocase
    condition:
        2 of them
}

rule Suspicious_Archive_Bomb {
    meta:
        description = "Detects potential archive bombs (zip bombs)"
        severity = "high"
    strings:
        $zip = { 50 4B 03 04 }
    condition:
        $zip at 0 and filesize < 1MB
}

Testing:

# Test YARA rules
yara /etc/yara/rules/tractatus-base.yar /path/to/test/files/

1.3 fail2ban Installation

# Install fail2ban
sudo apt install -y fail2ban

# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Start and enable
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

# Verify status
sudo fail2ban-client status

Configuration (will be completed in Phase 5 with monitoring integration)

1.4 Redis Installation (for Phase 4)

# Install Redis
sudo apt install -y redis-server

# Configure Redis for security
sudo nano /etc/redis/redis.conf
# Set: bind 127.0.0.1 ::1 (localhost only)
# Set: requirepass <strong-password>
# Set: maxmemory 256mb
# Set: maxmemory-policy allkeys-lru

# Restart Redis
sudo systemctl restart redis-server
sudo systemctl enable redis-server

# Test connection
redis-cli ping  # Should return PONG
redis-cli -a <password> ping  # Test with password

1.5 Email Stack Installation (for Phase 2)

# Install postfix and dovecot
sudo apt install -y postfix dovecot-core dovecot-imapd

# Install SpamAssassin
sudo apt install -y spamassassin spamc

# Install amavisd-new
sudo apt install -y amavisd-new

# Install OpenDKIM and opendmarc
sudo apt install -y opendkim opendkim-tools
sudo apt install -y opendmarc

Note: Detailed email configuration in Phase 2.

1.6 Logging Infrastructure

# Create log directories
sudo mkdir -p /var/log/tractatus
sudo mkdir -p /var/quarantine/tractatus
sudo mkdir -p /var/quarantine/email

# Set permissions
sudo chown -R tractatus:tractatus /var/log/tractatus
sudo chown -R tractatus:tractatus /var/quarantine
sudo chmod 750 /var/log/tractatus
sudo chmod 750 /var/quarantine

# Create security audit log
sudo touch /var/log/tractatus/security-audit.log
sudo chown tractatus:tractatus /var/log/tractatus/security-audit.log
sudo chmod 640 /var/log/tractatus/security-audit.log

Log Rotation Configuration (/etc/logrotate.d/tractatus):

/var/log/tractatus/*.log {
    daily
    rotate 90
    compress
    delaycompress
    notifempty
    create 0640 tractatus tractatus
    sharedscripts
    postrotate
        systemctl reload tractatus > /dev/null 2>&1 || true
    endscript
}

1.7 Communication Setup

ProtonMail Configuration:

  1. Create security team accounts:
    • security@tractatus.digital (primary security alerts)
    • admin@tractatus.digital (administrative notifications)
  2. Configure custom domain integration
  3. Set up email forwarding rules for critical alerts
  4. Test email delivery to all team members

Signal Setup:

  1. Create "Tractatus Security Team" group
  2. Add all team members with verified phone numbers
  3. Document escalation protocol:
    • Level 1 (Low): Email to security@tractatus.digital
    • Level 2 (Medium): Email + Signal text notification
    • Level 3 (High): Signal text + phone call
    • Level 4 (Critical): Signal text + immediate video call
  4. Test notification chain with dummy alert

1.8 Documentation Structure

# Create security documentation directories
mkdir -p /home/theflow/projects/tractatus/docs/security
mkdir -p /home/theflow/projects/tractatus/docs/security/incidents
mkdir -p /home/theflow/projects/tractatus/docs/security/audits
mkdir -p /home/theflow/projects/tractatus/docs/security/configurations

Create Initial Documents:

  • docs/security/SECURITY_POLICY.md (security policies and procedures)
  • docs/security/INCIDENT_RESPONSE.md (incident response playbook)
  • docs/security/ALERT_THRESHOLDS.md (alert configuration and escalation)
  • docs/security/TOOL_INVENTORY.md (list of all security tools and versions)

Testing & Verification

Phase 1 Success Criteria:

  • ClamAV scanning functional (tested with EICAR)
  • YARA rules loading without errors
  • fail2ban service running
  • Redis operational with authentication
  • Log directories created with correct permissions
  • Email stack installed (configuration in Phase 2)
  • ProtonMail accounts configured and tested
  • Signal group created with all team members
  • Security documentation structure in place

Rollback Plan:

  • All installations via apt can be removed with apt purge
  • Log directories can be safely deleted
  • No production impact in this phase

Phase 2: File & Email Security Implementation

Duration: 2-3 weeks Effort: 40-50 hours Dependencies: Phase 1 complete Risk: Medium

Objectives

  1. Implement file upload validation pipeline (inst_041)
  2. Configure email security stack (inst_042)
  3. Create quarantine management system
  4. Establish baseline security logging

Tasks

2.1 File Upload Validation Middleware (inst_041)

Create src/utils/security-logger.js:

const fs = require('fs').promises;
const path = require('path');

const SECURITY_LOG_PATH = '/var/log/tractatus/security-audit.log';

/**
 * Centralized security event logging
 * All security events must be logged here for audit trail
 */
async function logSecurityEvent(event) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    event_type: event.type,
    source_ip: event.sourceIp || 'unknown',
    user_id: event.userId || 'anonymous',
    endpoint: event.endpoint || 'unknown',
    user_agent: event.userAgent || 'unknown',
    violation_details: event.details || {},
    action_taken: event.action || 'logged',
    severity: event.severity || 'medium'
  };

  const logLine = JSON.stringify(logEntry) + '\n';

  try {
    await fs.appendFile(SECURITY_LOG_PATH, logLine, { encoding: 'utf-8' });
  } catch (error) {
    console.error('[SECURITY LOGGER ERROR]', error);
    // Fallback to console if file logging fails
    console.error('[SECURITY EVENT]', logEntry);
  }
}

module.exports = { logSecurityEvent };

Create src/middleware/file-security.middleware.js:

const multer = require('multer');
const { exec } = require('child_process');
const { promisify } = require('util');
const path = require('path');
const fs = require('fs').promises;
const execAsync = promisify(exec);
const { logSecurityEvent } = require('../utils/security-logger');

// File size limits (inst_041)
const FILE_LIMITS = {
  document: 10 * 1024 * 1024,  // 10MB
  media: 50 * 1024 * 1024,     // 50MB
  default: 5 * 1024 * 1024     // 5MB
};

// Quarantine directory
const QUARANTINE_DIR = '/var/quarantine/tractatus';

// Allowed MIME types per category
const ALLOWED_MIMES = {
  document: [
    'application/pdf',
    'text/plain',
    'text/markdown',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
  ],
  media: [
    'image/png',
    'image/jpeg',
    'image/gif',
    'video/mp4',
    'video/webm'
  ]
};

/**
 * Validate file type using file(1) command
 * Detects MIME type mismatch (e.g., .jpg file that's actually .exe)
 */
async function validateFileType(filePath, expectedMime) {
  try {
    const { stdout } = await execAsync(`file --mime-type -b "${filePath}"`);
    const actualMime = stdout.trim();

    if (actualMime !== expectedMime) {
      return {
        valid: false,
        reason: `MIME type mismatch: expected ${expectedMime}, got ${actualMime}`
      };
    }

    return { valid: true };
  } catch (error) {
    return {
      valid: false,
      reason: `File type validation failed: ${error.message}`
    };
  }
}

/**
 * Scan file with ClamAV
 */
async function scanWithClamAV(filePath) {
  try {
    // Use clamdscan (daemon) for faster scanning
    const { stdout, stderr } = await execAsync(`clamdscan --no-summary "${filePath}"`);

    // ClamAV returns exit code 1 if virus found
    return { clean: true };
  } catch (error) {
    // Check if it's a virus detection (not a system error)
    if (error.stdout && error.stdout.includes('FOUND')) {
      return {
        clean: false,
        threat: error.stdout.match(/: (.+) FOUND/)[1]
      };
    }

    // System error - treat as suspicious
    return {
      clean: false,
      threat: 'ClamAV scan error',
      error: error.message
    };
  }
}

/**
 * Scan file with YARA rules
 */
async function scanWithYARA(filePath) {
  try {
    const { stdout } = await execAsync(
      `yara /etc/yara/rules/tractatus-base.yar "${filePath}"`
    );

    if (stdout.trim()) {
      // YARA rule matched - suspicious patterns detected
      return {
        clean: false,
        matches: stdout.trim().split('\n')
      };
    }

    return { clean: true };
  } catch (error) {
    // YARA returns exit code 1 if no matches (which is good)
    if (error.code === 1 && !error.stdout) {
      return { clean: true };
    }

    return {
      clean: false,
      error: error.message
    };
  }
}

/**
 * Quarantine suspicious file
 */
async function quarantineFile(filePath, reason, metadata) {
  const filename = path.basename(filePath);
  const timestamp = Date.now();
  const quarantinePath = path.join(QUARANTINE_DIR, `${timestamp}_${filename}`);
  const metadataPath = `${quarantinePath}.json`;

  try {
    // Move file to quarantine
    await fs.rename(filePath, quarantinePath);

    // Write metadata
    await fs.writeFile(
      metadataPath,
      JSON.stringify({
        original_name: filename,
        quarantine_time: new Date().toISOString(),
        reason,
        metadata
      }, null, 2)
    );

    return quarantinePath;
  } catch (error) {
    console.error('[QUARANTINE ERROR]', error);
    throw error;
  }
}

/**
 * Main file security validation middleware
 */
function createFileSecurityMiddleware(fileCategory = 'default') {
  // Configure multer for temporary storage
  const upload = multer({
    dest: '/tmp/tractatus-uploads',
    limits: {
      fileSize: FILE_LIMITS[fileCategory] || FILE_LIMITS.default
    }
  });

  return [
    upload.single('file'),
    async (req, res, next) => {
      if (!req.file) {
        return next();  // No file uploaded
      }

      const { originalname, mimetype, path: tempPath, size } = req.file;
      const clientIp = req.ip || req.connection.remoteAddress;

      try {
        // Step 1: File type validation
        const typeValidation = await validateFileType(tempPath, mimetype);
        if (!typeValidation.valid) {
          await logSecurityEvent({
            type: 'file_upload_rejected',
            sourceIp: clientIp,
            userId: req.user?.id,
            endpoint: req.path,
            userAgent: req.get('user-agent'),
            details: {
              filename: originalname,
              reason: typeValidation.reason,
              size
            },
            action: 'rejected',
            severity: 'medium'
          });

          await fs.unlink(tempPath);  // Delete temp file
          return res.status(400).json({
            error: 'File validation failed',
            message: 'File type does not match extension'
          });
        }

        // Step 2: ClamAV scan
        const clamavResult = await scanWithClamAV(tempPath);
        if (!clamavResult.clean) {
          const quarantinePath = await quarantineFile(tempPath, 'malware_detected', {
            threat: clamavResult.threat,
            scanner: 'ClamAV',
            originalname,
            clientIp
          });

          await logSecurityEvent({
            type: 'malware_detected',
            sourceIp: clientIp,
            userId: req.user?.id,
            endpoint: req.path,
            userAgent: req.get('user-agent'),
            details: {
              filename: originalname,
              threat: clamavResult.threat,
              quarantine_path: quarantinePath,
              size
            },
            action: 'quarantined',
            severity: 'high'
          });

          return res.status(400).json({
            error: 'Security threat detected',
            message: 'File has been quarantined for manual review'
          });
        }

        // Step 3: YARA scan
        const yaraResult = await scanWithYARA(tempPath);
        if (!yaraResult.clean) {
          const quarantinePath = await quarantineFile(tempPath, 'suspicious_patterns', {
            matches: yaraResult.matches,
            scanner: 'YARA',
            originalname,
            clientIp
          });

          await logSecurityEvent({
            type: 'suspicious_patterns_detected',
            sourceIp: clientIp,
            userId: req.user?.id,
            endpoint: req.path,
            userAgent: req.get('user-agent'),
            details: {
              filename: originalname,
              patterns: yaraResult.matches,
              quarantine_path: quarantinePath,
              size
            },
            action: 'quarantined',
            severity: 'high'
          });

          return res.status(400).json({
            error: 'Suspicious patterns detected',
            message: 'File has been quarantined for manual review'
          });
        }

        // All checks passed - file is safe
        await logSecurityEvent({
          type: 'file_upload_accepted',
          sourceIp: clientIp,
          userId: req.user?.id,
          endpoint: req.path,
          userAgent: req.get('user-agent'),
          details: {
            filename: originalname,
            size,
            mimetype
          },
          action: 'accepted',
          severity: 'low'
        });

        // Attach validated file info to request
        req.validatedFile = {
          path: tempPath,
          originalname,
          mimetype,
          size
        };

        next();

      } catch (error) {
        console.error('[FILE SECURITY ERROR]', error);

        await logSecurityEvent({
          type: 'file_validation_error',
          sourceIp: clientIp,
          userId: req.user?.id,
          endpoint: req.path,
          userAgent: req.get('user-agent'),
          details: {
            filename: originalname,
            error: error.message
          },
          action: 'rejected',
          severity: 'high'
        });

        // Clean up temp file
        try {
          await fs.unlink(tempPath);
        } catch (unlinkError) {
          console.error('[FILE CLEANUP ERROR]', unlinkError);
        }

        return res.status(500).json({
          error: 'File validation failed',
          message: 'An error occurred during security validation'
        });
      }
    }
  ];
}

module.exports = {
  createFileSecurityMiddleware,
  validateFileType,
  scanWithClamAV,
  scanWithYARA
};

Integration Example (src/routes/cases.routes.js):

const { createFileSecurityMiddleware } = require('../middleware/file-security.middleware');

// Apply to case study submission endpoint
router.post('/submit',
  createFileSecurityMiddleware('document'),  // 10MB limit for documents
  casesController.submitCase
);

2.2 Email Security Configuration (inst_042)

Detailed email configuration will go here - this section is substantial and includes:

  • Postfix main.cf configuration
  • SpamAssassin custom rules for governance domain
  • amavisd-new integration with ClamAV
  • DKIM/SPF/DMARC setup
  • Dovecot configuration
  • Email quarantine management

Note: This section is extensive and would add significant length. Should I continue with full email configuration details, or would you prefer a summary approach for this phase?

2.3 Testing & Verification

File Upload Testing:

# Test with clean file
curl -X POST -F "file=@clean-document.pdf" http://localhost:9000/api/cases/submit

# Test with EICAR malware sample
curl -X POST -F "file=@eicar.txt" http://localhost:9000/api/cases/submit
# Expected: Rejected with "malware detected", file quarantined

# Test with MIME type mismatch
cp malicious.exe fake-doc.pdf
curl -X POST -F "file=@fake-doc.pdf" http://localhost:9000/api/cases/submit
# Expected: Rejected with "MIME type mismatch"

# Verify security logging
tail -f /var/log/tractatus/security-audit.log

# Check quarantine directory
ls -lh /var/quarantine/tractatus/

Phase 2 Success Criteria

  • File upload middleware deployed to all upload endpoints
  • ClamAV scanning functional with malware detection
  • YARA pattern matching operational
  • File quarantine system working
  • Security events logged to audit trail
  • Email stack configured (postfix, SpamAssassin, amavisd-new)
  • DKIM/SPF/DMARC validation operational
  • Email quarantine functional
  • 100% test coverage for file validation pipeline
  • No false positives with legitimate files

Rollback Plan:

  • Remove middleware from routes
  • Restore previous upload handling
  • Email configuration changes documented for reversal

Phase 3: Application Security (Input Validation & HTTP Headers)

Duration: 1-2 weeks Effort: 30-40 hours Dependencies: Phase 1 complete (Phase 2 can run in parallel) Risk: Low-Medium

Objectives

  1. Implement form input sanitization (inst_043)
  2. Deploy HTTP security headers (inst_044)
  3. Add CSRF protection
  4. Configure CSP violation reporting

Tasks

3.1 Input Validation Middleware (inst_043)

Install Dependencies:

npm install dompurify validator jsdom csurf express-rate-limit

Create src/middleware/input-validation.middleware.js:

const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const validator = require('validator');
const { logSecurityEvent } = require('../utils/security-logger');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

// Input length limits per field type (inst_043)
const LENGTH_LIMITS = {
  email: 254,
  url: 2048,
  phone: 20,
  name: 100,
  title: 200,
  description: 5000,
  case_study: 50000,
  default: 5000
};

// Safe HTML tags for markdown fields
const SAFE_HTML_TAGS = ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'code', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];

/**
 * Sanitize HTML using DOMPurify
 */
function sanitizeHTML(input, allowMarkdown = false) {
  if (typeof input !== 'string') return '';

  const config = allowMarkdown
    ? { ALLOWED_TAGS: SAFE_HTML_TAGS, ALLOWED_ATTR: ['href'] }
    : { ALLOWED_TAGS: [], ALLOWED_ATTR: [] };  // Strip all HTML

  return DOMPurify.sanitize(input, config);
}

/**
 * Validate input against expected data type
 */
function validateDataType(value, type, fieldName) {
  const errors = [];

  switch (type) {
    case 'email':
      if (!validator.isEmail(value)) {
        errors.push(`${fieldName} must be a valid email address`);
      }
      break;

    case 'url':
      if (!validator.isURL(value, { require_protocol: true })) {
        errors.push(`${fieldName} must be a valid URL`);
      }
      break;

    case 'phone':
      if (!validator.isMobilePhone(value, 'any', { strictMode: false })) {
        errors.push(`${fieldName} must be a valid phone number`);
      }
      break;

    case 'numeric':
      if (!validator.isNumeric(value)) {
        errors.push(`${fieldName} must be numeric`);
      }
      break;

    case 'alphanumeric':
      if (!validator.isAlphanumeric(value, 'en-US', { ignore: ' -_' })) {
        errors.push(`${fieldName} must be alphanumeric`);
      }
      break;

    case 'text':
      // No additional validation for plain text
      break;

    default:
      errors.push(`Unknown data type for ${fieldName}`);
  }

  return errors;
}

/**
 * Check for NoSQL injection patterns
 */
function detectNoSQLInjection(value) {
  if (typeof value === 'object') {
    // Check for MongoDB operators
    const dangerousOps = ['$where', '$ne', '$gt', '$lt', '$regex', '$in', '$nin'];
    const keys = Object.keys(value);

    for (const key of keys) {
      if (dangerousOps.includes(key)) {
        return { detected: true, operator: key };
      }

      // Recursive check
      if (typeof value[key] === 'object') {
        const result = detectNoSQLInjection(value[key]);
        if (result.detected) return result;
      }
    }
  }

  return { detected: false };
}

/**
 * Check for XSS patterns
 */
function detectXSS(value) {
  if (typeof value !== 'string') return { detected: false };

  const xssPatterns = [
    /<script[^>]*>.*?<\/script>/gi,
    /javascript:/gi,
    /on\w+\s*=/gi,  // Event handlers: onclick=, onload=, etc.
    /<iframe/gi,
    /eval\(/gi,
    /expression\(/gi
  ];

  for (const pattern of xssPatterns) {
    if (pattern.test(value)) {
      return { detected: true, pattern: pattern.toString() };
    }
  }

  return { detected: false };
}

/**
 * Main input validation middleware
 */
function createInputValidationMiddleware(schema) {
  return async (req, res, next) => {
    const clientIp = req.ip || req.connection.remoteAddress;
    const errors = [];
    const sanitized = {};

    try {
      for (const [field, config] of Object.entries(schema)) {
        const value = req.body[field];

        // Required field check
        if (config.required && !value) {
          errors.push(`${field} is required`);
          continue;
        }

        // Skip validation if field is optional and not provided
        if (!value && !config.required) {
          continue;
        }

        // Length validation
        const maxLength = config.maxLength || LENGTH_LIMITS[config.type] || LENGTH_LIMITS.default;
        if (value && value.length > maxLength) {
          errors.push(`${field} exceeds maximum length of ${maxLength} characters`);
          continue;
        }

        // Data type validation
        if (config.type) {
          const typeErrors = validateDataType(value, config.type, field);
          errors.push(...typeErrors);
        }

        // NoSQL injection detection
        const injectionCheck = detectNoSQLInjection(value);
        if (injectionCheck.detected) {
          await logSecurityEvent({
            type: 'nosql_injection_attempt',
            sourceIp: clientIp,
            userId: req.user?.id,
            endpoint: req.path,
            userAgent: req.get('user-agent'),
            details: {
              field,
              operator: injectionCheck.operator,
              value: JSON.stringify(value).substring(0, 200)
            },
            action: 'blocked',
            severity: 'high'
          });

          errors.push(`${field} contains invalid characters`);
          continue;
        }

        // XSS detection (before sanitization)
        const xssCheck = detectXSS(value);
        if (xssCheck.detected) {
          await logSecurityEvent({
            type: 'xss_attempt',
            sourceIp: clientIp,
            userId: req.user?.id,
            endpoint: req.path,
            userAgent: req.get('user-agent'),
            details: {
              field,
              pattern: xssCheck.pattern,
              value: String(value).substring(0, 200)
            },
            action: 'sanitized',
            severity: 'high'
          });
        }

        // HTML sanitization
        if (typeof value === 'string') {
          sanitized[field] = sanitizeHTML(value, config.allowMarkdown || false);
        } else {
          sanitized[field] = value;
        }
      }

      // If validation errors, reject request
      if (errors.length > 0) {
        await logSecurityEvent({
          type: 'input_validation_failure',
          sourceIp: clientIp,
          userId: req.user?.id,
          endpoint: req.path,
          userAgent: req.get('user-agent'),
          details: {
            errors,
            fields: Object.keys(schema)
          },
          action: 'rejected',
          severity: 'medium'
        });

        return res.status(400).json({
          error: 'Validation failed',
          details: errors
        });
      }

      // Replace req.body with sanitized values
      req.body = { ...req.body, ...sanitized };
      req.validationPassed = true;

      next();

    } catch (error) {
      console.error('[INPUT VALIDATION ERROR]', error);

      await logSecurityEvent({
        type: 'input_validation_error',
        sourceIp: clientIp,
        userId: req.user?.id,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          error: error.message
        },
        action: 'rejected',
        severity: 'high'
      });

      return res.status(500).json({
        error: 'Validation failed',
        message: 'An error occurred during input validation'
      });
    }
  };
}

/**
 * Rate limiting for form submissions
 */
const rateLimit = require('express-rate-limit');

const formRateLimiter = rateLimit({
  windowMs: 60 * 1000,  // 1 minute
  max: 5,  // 5 requests per minute per IP (inst_043)
  message: 'Too many requests from this IP, please try again later',
  standardHeaders: true,
  legacyHeaders: false,
  handler: async (req, res) => {
    await logSecurityEvent({
      type: 'rate_limit_exceeded',
      sourceIp: req.ip,
      userId: req.user?.id,
      endpoint: req.path,
      userAgent: req.get('user-agent'),
      details: {
        limit: 5,
        window: '1 minute'
      },
      action: 'blocked',
      severity: 'medium'
    });

    res.status(429).json({
      error: 'Rate limit exceeded',
      message: 'Too many requests, please try again later'
    });
  }
});

module.exports = {
  createInputValidationMiddleware,
  formRateLimiter,
  sanitizeHTML,
  validateDataType,
  detectNoSQLInjection,
  detectXSS
};

Integration Example (src/routes/cases.routes.js):

const { createInputValidationMiddleware, formRateLimiter } = require('../middleware/input-validation.middleware');

const caseSubmissionSchema = {
  title: { type: 'text', required: true, maxLength: 200 },
  description: { type: 'text', required: true, maxLength: 50000, allowMarkdown: true },
  contact_email: { type: 'email', required: true },
  contact_name: { type: 'text', required: true, maxLength: 100 },
  organization: { type: 'text', required: false, maxLength: 200 }
};

router.post('/submit',
  formRateLimiter,  // Rate limiting
  createInputValidationMiddleware(caseSubmissionSchema),  // Input validation
  casesController.submitCase
);

3.2 HTTP Security Headers (inst_044)

Install helmet.js (optional convenience wrapper):

npm install helmet

Create src/middleware/security-headers.middleware.js:

/**
 * Security headers middleware (inst_044)
 * Implements comprehensive HTTP security headers
 */
function securityHeadersMiddleware(req, res, next) {
  // Content Security Policy (enforces inst_008 at HTTP level)
  res.setHeader(
    'Content-Security-Policy',
    [
      "default-src 'self'",
      "script-src 'self'",
      "style-src 'self' 'unsafe-inline'",  // Allow inline styles for Tailwind
      "img-src 'self' data: https:",
      "font-src 'self' https://fonts.gstatic.com",
      "connect-src 'self'",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'",
      "upgrade-insecure-requests",
      "block-all-mixed-content",
      "report-uri /api/csp-violations"
    ].join('; ')
  );

  // Prevent MIME type sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Prevent clickjacking
  res.setHeader('X-Frame-Options', 'DENY');

  // Enable browser XSS filter
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Enforce HTTPS (HSTS)
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );

  // Limit referrer information leakage
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');

  // Restrict dangerous browser features
  res.setHeader(
    'Permissions-Policy',
    'geolocation=(), microphone=(), camera=(), payment=()'
  );

  next();
}

module.exports = { securityHeadersMiddleware };

Apply Globally (src/server.js):

const { securityHeadersMiddleware } = require('./middleware/security-headers.middleware');

// Apply security headers to ALL routes
app.use(securityHeadersMiddleware);

// All other middleware and routes after this

3.3 CSRF Protection

Install csurf:

npm install csurf cookie-parser

Configure CSRF (src/server.js):

const csrf = require('csurf');
const cookieParser = require('cookie-parser');

// Cookie parser required for CSRF tokens
app.use(cookieParser());

// CSRF protection for state-changing operations
const csrfProtection = csrf({ cookie: true });

// Apply to all POST/PUT/DELETE/PATCH routes
app.use((req, res, next) => {
  if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
    return csrfProtection(req, res, next);
  }
  next();
});

// CSRF error handler
app.use((err, req, res, next) => {
  if (err.code !== 'EBADCSRFTOKEN') return next(err);

  // Log CSRF violation
  logSecurityEvent({
    type: 'csrf_violation',
    sourceIp: req.ip,
    userId: req.user?.id,
    endpoint: req.path,
    userAgent: req.get('user-agent'),
    details: {
      method: req.method
    },
    action: 'blocked',
    severity: 'high'
  });

  res.status(403).json({
    error: 'Invalid CSRF token',
    message: 'Request blocked for security reasons'
  });
});

Generate CSRF Tokens for Forms:

// Endpoint to get CSRF token
app.get('/api/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

Client-side Integration:

// Fetch CSRF token before form submission
async function submitForm(formData) {
  const tokenResponse = await fetch('/api/csrf-token');
  const { csrfToken } = await tokenResponse.json();

  formData.append('_csrf', csrfToken);

  const response = await fetch('/api/cases/submit', {
    method: 'POST',
    body: formData
  });

  return response.json();
}

3.4 CSP Violation Reporting

Create CSP Violation Endpoint (src/routes/security.routes.js):

const express = require('express');
const router = express.Router();
const { logSecurityEvent } = require('../utils/security-logger');

/**
 * CSP violation reporting endpoint
 * Receives reports from browser when CSP is violated
 */
router.post('/csp-violations', express.json({ type: 'application/csp-report' }), async (req, res) => {
  const report = req.body['csp-report'];

  if (report) {
    await logSecurityEvent({
      type: 'csp_violation',
      sourceIp: req.ip,
      userId: req.user?.id,
      endpoint: report['document-uri'],
      userAgent: req.get('user-agent'),
      details: {
        blocked_uri: report['blocked-uri'],
        violated_directive: report['violated-directive'],
        original_policy: report['original-policy'],
        source_file: report['source-file'],
        line_number: report['line-number']
      },
      action: 'reported',
      severity: 'medium'
    });
  }

  res.status(204).end();  // No content response
});

module.exports = router;

Register Route (src/server.js):

const securityRoutes = require('./routes/security.routes');
app.use('/api', securityRoutes);

Phase 3 Testing

Input Validation Tests:

# Test XSS injection
curl -X POST http://localhost:9000/api/cases/submit \
  -H "Content-Type: application/json" \
  -d '{"title":"<script>alert(1)</script>","description":"test"}'
# Expected: Sanitized, script tags removed

# Test NoSQL injection
curl -X POST http://localhost:9000/api/cases/submit \
  -H "Content-Type: application/json" \
  -d '{"title":{"$ne":null},"description":"test"}'
# Expected: Rejected, logged as injection attempt

# Test rate limiting
for i in {1..10}; do
  curl -X POST http://localhost:9000/api/cases/submit \
    -H "Content-Type: application/json" \
    -d '{"title":"test","description":"test"}'
done
# Expected: First 5 succeed, remaining rejected with 429

Security Headers Verification:

# Check headers
curl -I http://localhost:9000
# Expected: All security headers present

# Use SecurityHeaders.com
# Visit: https://securityheaders.com/?q=https://agenticgovernance.digital
# Expected: Grade A or A+

CSRF Protection Test:

# Request without CSRF token
curl -X POST http://localhost:9000/api/cases/submit \
  -H "Content-Type: application/json" \
  -d '{"title":"test","description":"test"}'
# Expected: 403 Forbidden, CSRF violation logged

Phase 3 Success Criteria

  • Input validation middleware deployed to all form endpoints
  • HTML sanitization functional (XSS patterns removed)
  • NoSQL injection detection operational
  • Rate limiting active (5 req/min on form endpoints)
  • All security headers present in HTTP responses
  • SecurityHeaders.com grade A or A+
  • CSRF protection active on POST/PUT/DELETE
  • CSP violation reporting functional
  • 100% test coverage for input validation
  • Zero false positives with legitimate input

Phase 4: API Protection & Rate Limiting

Duration: 1-2 weeks Effort: 30-40 hours Dependencies: Phase 1 complete (Redis installed) Risk: Medium

Objectives

  1. Implement tiered rate limiting (inst_045)
  2. Deploy JWT authentication
  3. Add IP blocking for repeated violations
  4. Configure request validation
  5. Implement response sanitization

Tasks

4.1 JWT Authentication System

Install Dependencies:

npm install jsonwebtoken bcryptjs

Create src/middleware/auth.middleware.js:

const jwt = require('jsonwebtoken');
const { logSecurityEvent } = require('../utils/security-logger');

const JWT_SECRET = process.env.JWT_SECRET;
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;

// Token expiry times (inst_045)
const ACCESS_TOKEN_EXPIRY = '15m';   // 15 minutes
const REFRESH_TOKEN_EXPIRY = '7d';   // 7 days

/**
 * Generate access token
 */
function generateAccessToken(user) {
  return jwt.sign(
    {
      userId: user._id,
      email: user.email,
      role: user.role
    },
    JWT_SECRET,
    {
      expiresIn: ACCESS_TOKEN_EXPIRY,
      audience: 'tractatus-api',
      issuer: 'tractatus'
    }
  );
}

/**
 * Generate refresh token
 */
function generateRefreshToken(user) {
  return jwt.sign(
    {
      userId: user._id
    },
    JWT_REFRESH_SECRET,
    {
      expiresIn: REFRESH_TOKEN_EXPIRY,
      audience: 'tractatus-refresh',
      issuer: 'tractatus'
    }
  );
}

/**
 * Verify JWT token
 */
function verifyToken(token, secret = JWT_SECRET) {
  try {
    return jwt.verify(token, secret);
  } catch (error) {
    return null;
  }
}

/**
 * Authentication middleware
 */
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];  // Bearer TOKEN

  if (!token) {
    return res.status(401).json({
      error: 'Authentication required',
      message: 'No token provided'
    });
  }

  const decoded = verifyToken(token);

  if (!decoded) {
    logSecurityEvent({
      type: 'authentication_failure',
      sourceIp: req.ip,
      userId: null,
      endpoint: req.path,
      userAgent: req.get('user-agent'),
      details: {
        reason: 'Invalid or expired token'
      },
      action: 'rejected',
      severity: 'medium'
    });

    return res.status(403).json({
      error: 'Invalid token',
      message: 'Token is invalid or expired'
    });
  }

  req.user = decoded;
  next();
}

/**
 * Role-based authorization middleware
 */
function requireRole(role) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({
        error: 'Authentication required'
      });
    }

    if (req.user.role !== role && req.user.role !== 'admin') {
      logSecurityEvent({
        type: 'authorization_failure',
        sourceIp: req.ip,
        userId: req.user.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          required_role: role,
          user_role: req.user.role
        },
        action: 'rejected',
        severity: 'medium'
      });

      return res.status(403).json({
        error: 'Insufficient permissions',
        message: `This endpoint requires ${role} role`
      });
    }

    next();
  };
}

module.exports = {
  generateAccessToken,
  generateRefreshToken,
  verifyToken,
  authenticateToken,
  requireRole
};

4.2 Tiered Rate Limiting

Configure Redis Client (src/utils/redis-client.js):

const Redis = require('ioredis');

const redisClient = new Redis({
  host: '127.0.0.1',
  port: 6379,
  password: process.env.REDIS_PASSWORD,
  db: 0,
  retryStrategy: (times) => {
    const delay = Math.min(times * 50, 2000);
    return delay;
  }
});

redisClient.on('error', (err) => {
  console.error('[REDIS ERROR]', err);
});

redisClient.on('connect', () => {
  console.log('[REDIS] Connected successfully');
});

module.exports = redisClient;

Create src/middleware/rate-limit.middleware.js:

const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redisClient = require('../utils/redis-client');
const { logSecurityEvent } = require('../utils/security-logger');

/**
 * Create rate limiter with Redis store for distributed rate limiting
 */
function createRateLimiter(options) {
  const {
    windowMs,
    max,
    tier,
    skipSuccessfulRequests = false,
    keyGenerator = (req) => req.ip
  } = options;

  return rateLimit({
    windowMs,
    max,
    skipSuccessfulRequests,
    keyGenerator,
    standardHeaders: true,
    legacyHeaders: false,
    store: new RedisStore({
      client: redisClient,
      prefix: `rl:${tier}:`
    }),
    handler: async (req, res) => {
      await logSecurityEvent({
        type: 'rate_limit_violation',
        sourceIp: req.ip,
        userId: req.user?.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          tier,
          limit: max,
          window: `${windowMs / 1000}s`
        },
        action: 'blocked',
        severity: 'medium'
      });

      res.status(429).json({
        error: 'Rate limit exceeded',
        message: `Too many requests. Limit: ${max} per ${windowMs / 1000} seconds`,
        retryAfter: Math.ceil(windowMs / 1000)
      });
    }
  });
}

// Public endpoints: 100 requests per 15 minutes per IP (inst_045)
const publicRateLimiter = createRateLimiter({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 100,
  tier: 'public'
});

// Authenticated endpoints: 1000 requests per 15 minutes per user (inst_045)
const authenticatedRateLimiter = createRateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 1000,
  tier: 'authenticated',
  keyGenerator: (req) => req.user?.userId || req.ip
});

// Admin endpoints: 50 requests per 15 minutes per admin (inst_045)
const adminRateLimiter = createRateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 50,
  tier: 'admin',
  keyGenerator: (req) => req.user?.userId || req.ip
});

/**
 * IP blocking after repeated rate limit violations
 * 10 violations in 1 hour = 24 hour block (inst_045)
 */
const BLOCK_THRESHOLD = 10;
const BLOCK_WINDOW = 60 * 60 * 1000;  // 1 hour
const BLOCK_DURATION = 24 * 60 * 60 * 1000;  // 24 hours

async function checkIPBlock(req, res, next) {
  const ip = req.ip;
  const blockKey = `blocked:${ip}`;
  const violationKey = `violations:${ip}`;

  try {
    // Check if IP is currently blocked
    const isBlocked = await redisClient.get(blockKey);
    if (isBlocked) {
      await logSecurityEvent({
        type: 'blocked_ip_attempt',
        sourceIp: ip,
        userId: req.user?.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          reason: 'IP blocked due to repeated violations',
          block_expires: new Date(parseInt(isBlocked)).toISOString()
        },
        action: 'blocked',
        severity: 'high'
      });

      return res.status(403).json({
        error: 'Access denied',
        message: 'Your IP has been temporarily blocked due to suspicious activity',
        unblockTime: new Date(parseInt(isBlocked)).toISOString()
      });
    }

    // Track violations
    if (res.statusCode === 429) {  // Rate limit exceeded
      const violations = await redisClient.incr(violationKey);
      await redisClient.expire(violationKey, Math.ceil(BLOCK_WINDOW / 1000));

      if (violations >= BLOCK_THRESHOLD) {
        // Block IP
        const unblockTime = Date.now() + BLOCK_DURATION;
        await redisClient.setex(blockKey, Math.ceil(BLOCK_DURATION / 1000), unblockTime.toString());

        await logSecurityEvent({
          type: 'ip_blocked',
          sourceIp: ip,
          userId: req.user?.userId,
          endpoint: req.path,
          userAgent: req.get('user-agent'),
          details: {
            reason: 'Exceeded violation threshold',
            violations,
            threshold: BLOCK_THRESHOLD,
            duration: '24 hours'
          },
          action: 'blocked',
          severity: 'high'
        });

        return res.status(403).json({
          error: 'IP blocked',
          message: 'Your IP has been blocked due to repeated rate limit violations',
          unblockTime: new Date(unblockTime).toISOString()
        });
      }
    }

    next();
  } catch (error) {
    console.error('[IP BLOCK CHECK ERROR]', error);
    next();  // Fail open - don't block legitimate users due to Redis errors
  }
}

module.exports = {
  publicRateLimiter,
  authenticatedRateLimiter,
  adminRateLimiter,
  checkIPBlock,
  createRateLimiter
};

4.3 API Request Validation

Create src/middleware/api-validation.middleware.js:

const { logSecurityEvent } = require('../utils/security-logger');

/**
 * Validate request content type
 */
function validateContentType(req, res, next) {
  if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
    const contentType = req.get('content-type');

    if (!contentType || !contentType.includes('application/json')) {
      logSecurityEvent({
        type: 'invalid_content_type',
        sourceIp: req.ip,
        userId: req.user?.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          method: req.method,
          content_type: contentType || 'none'
        },
        action: 'rejected',
        severity: 'low'
      });

      return res.status(415).json({
        error: 'Unsupported Media Type',
        message: 'Content-Type must be application/json'
      });
    }
  }

  next();
}

/**
 * Validate payload size (inst_045: max 1MB)
 */
function validatePayloadSize(maxSize = 1024 * 1024) {  // 1MB default
  return (req, res, next) => {
    const contentLength = parseInt(req.get('content-length') || '0');

    if (contentLength > maxSize) {
      logSecurityEvent({
        type: 'payload_size_exceeded',
        sourceIp: req.ip,
        userId: req.user?.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          size: contentLength,
          max_size: maxSize
        },
        action: 'rejected',
        severity: 'medium'
      });

      return res.status(413).json({
        error: 'Payload Too Large',
        message: `Request body must not exceed ${maxSize} bytes`
      });
    }

    next();
  };
}

/**
 * Reject unexpected fields in request body
 */
function rejectUnexpectedFields(allowedFields) {
  return (req, res, next) => {
    const bodyFields = Object.keys(req.body || {});
    const unexpected = bodyFields.filter(field => !allowedFields.includes(field));

    if (unexpected.length > 0) {
      logSecurityEvent({
        type: 'unexpected_fields',
        sourceIp: req.ip,
        userId: req.user?.userId,
        endpoint: req.path,
        userAgent: req.get('user-agent'),
        details: {
          unexpected_fields: unexpected,
          allowed_fields: allowedFields
        },
        action: 'rejected',
        severity: 'low'
      });

      return res.status(400).json({
        error: 'Invalid request',
        message: 'Request contains unexpected fields',
        unexpected: unexpected
      });
    }

    next();
  };
}

module.exports = {
  validateContentType,
  validatePayloadSize,
  rejectUnexpectedFields
};

4.4 Response Sanitization (inst_045, inst_013)

Create src/middleware/response-sanitization.middleware.js:

/**
 * Sanitize error responses to prevent information disclosure (inst_013, inst_045)
 * NEVER expose: stack traces, internal paths, environment details
 */
function sanitizeErrorResponse(err, req, res, next) {
  const isProduction = process.env.NODE_ENV === 'production';

  // Log full error details internally
  console.error('[ERROR]', {
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method,
    ip: req.ip,
    user: req.user?.userId
  });

  // Determine status code
  const statusCode = err.statusCode || err.status || 500;

  // Generic error response for production
  if (isProduction) {
    const genericErrors = {
      400: 'Bad Request',
      401: 'Unauthorized',
      403: 'Forbidden',
      404: 'Not Found',
      429: 'Too Many Requests',
      500: 'Internal Server Error',
      502: 'Bad Gateway',
      503: 'Service Unavailable'
    };

    return res.status(statusCode).json({
      error: genericErrors[statusCode] || 'Error',
      message: err.message || 'An error occurred',
      // NEVER include: stack, file paths, internal details
    });
  }

  // Development mode: more detailed errors (but still sanitized)
  res.status(statusCode).json({
    error: err.name || 'Error',
    message: err.message,
    statusCode,
    // Stack trace only in development
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
}

/**
 * Remove sensitive fields from API responses
 */
function removeSensitiveFields(data, sensitiveFields = ['password', 'passwordHash', 'apiKey', 'secret']) {
  if (Array.isArray(data)) {
    return data.map(item => removeSensitiveFields(item, sensitiveFields));
  }

  if (typeof data === 'object' && data !== null) {
    const sanitized = { ...data };

    for (const field of sensitiveFields) {
      delete sanitized[field];
    }

    // Recursively sanitize nested objects
    for (const key in sanitized) {
      if (typeof sanitized[key] === 'object') {
        sanitized[key] = removeSensitiveFields(sanitized[key], sensitiveFields);
      }
    }

    return sanitized;
  }

  return data;
}

module.exports = {
  sanitizeErrorResponse,
  removeSensitiveFields
};

Apply Globally (src/server.js):

const { sanitizeErrorResponse } = require('./middleware/response-sanitization.middleware');
const { checkIPBlock } = require('./middleware/rate-limit.middleware');

// IP blocking check (before routes)
app.use(checkIPBlock);

// All routes here...

// Error handling (after all routes)
app.use(sanitizeErrorResponse);

4.5 Route Configuration with Rate Limiting

Example Route Configuration (src/routes/index.js):

const { publicRateLimiter, authenticatedRateLimiter, adminRateLimiter } = require('../middleware/rate-limit.middleware');
const { authenticateToken, requireRole } = require('../middleware/auth.middleware');
const { validateContentType, validatePayloadSize } = require('../middleware/api-validation.middleware');

// Public routes
app.use('/api/documents', publicRateLimiter, documentsRoutes);
app.use('/api/blog', publicRateLimiter, blogRoutes);

// Authenticated routes
app.use('/api/cases',
  authenticatedRateLimiter,
  authenticateToken,
  validateContentType,
  validatePayloadSize(),
  casesRoutes
);

app.use('/api/media',
  authenticatedRateLimiter,
  authenticateToken,
  validateContentType,
  validatePayloadSize(),
  mediaRoutes
);

// Admin routes
app.use('/api/admin',
  adminRateLimiter,
  authenticateToken,
  requireRole('admin'),
  validateContentType,
  validatePayloadSize(),
  adminRoutes
);

app.use('/api/governance',
  adminRateLimiter,
  authenticateToken,
  requireRole('admin'),
  governanceRoutes
);

Phase 4 Testing

Rate Limiting Tests:

# Test public rate limit (100 req/15min)
for i in {1..110}; do
  curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9000/api/documents
done
# Expected: First 100 return 200, remaining return 429

# Test authenticated rate limit
TOKEN="<valid-jwt-token>"
for i in {1..1010}; do
  curl -s -o /dev/null -w "%{http_code}\n" \
    -H "Authorization: Bearer $TOKEN" \
    http://localhost:9000/api/cases
done
# Expected: First 1000 return 200, remaining return 429

# Test IP blocking (10 violations = block)
for i in {1..15}; do
  # Exceed rate limit to trigger violations
  for j in {1..10}; do
    curl http://localhost:9000/api/documents
  done
  sleep 1
done
# Expected: After ~10 violations, 403 Forbidden for 24 hours

Authentication Tests:

# Request without token
curl http://localhost:9000/api/cases
# Expected: 401 Unauthorized

# Request with invalid token
curl -H "Authorization: Bearer invalid-token" http://localhost:9000/api/cases
# Expected: 403 Forbidden, logged as authentication failure

# Request with expired token
# (Create token with 1-second expiry, wait, then use)
# Expected: 403 Forbidden

API Validation Tests:

# Wrong content type
curl -X POST http://localhost:9000/api/cases \
  -H "Content-Type: text/plain" \
  -d "test"
# Expected: 415 Unsupported Media Type

# Oversized payload
curl -X POST http://localhost:9000/api/cases \
  -H "Content-Type: application/json" \
  -d "$(head -c 2M < /dev/urandom | base64)"
# Expected: 413 Payload Too Large

Phase 4 Success Criteria

  • Tiered rate limiting operational (public/authenticated/admin)
  • Redis storing rate limit counters
  • JWT authentication functional (15min access, 7day refresh)
  • Role-based authorization working
  • IP blocking after 10 violations
  • Content-type validation on POST/PUT/PATCH
  • Payload size limits enforced (1MB max)
  • Unexpected fields rejected
  • Error responses sanitized (no stack traces in production)
  • All security events logged
  • 100% test coverage for authentication/authorization

Phase 5: Security Monitoring & Alerting

Duration: 2-3 weeks Effort: 40-50 hours Dependencies: Phase 1-4 complete Risk: Low

Objectives

  1. Implement security monitoring dashboard (inst_046)
  2. Configure fail2ban integration
  3. Set up ProtonMail alert system
  4. Create Signal notification automation
  5. Generate weekly security reports
  6. Establish incident response procedures

Tasks

5.1 Security Monitoring Dashboard

Create Frontend (public/admin/security-monitoring.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Security Monitoring | Tractatus</title>
    <link rel="stylesheet" href="/css/tailwind.css">
</head>
<body class="bg-gray-50">
    <div class="container mx-auto px-4 py-8">
        <h1 class="text-3xl font-bold mb-8">Security Monitoring Dashboard</h1>

        <!-- Time Range Selector -->
        <div class="mb-6">
            <label class="font-semibold mr-4">Time Range:</label>
            <select id="time-range" class="border rounded px-4 py-2">
                <option value="1h">Last Hour</option>
                <option value="24h" selected>Last 24 Hours</option>
                <option value="7d">Last 7 Days</option>
                <option value="30d">Last 30 Days</option>
            </select>
        </div>

        <!-- Key Metrics Grid -->
        <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Rejected Uploads</h3>
                <p class="text-3xl font-bold" id="metric-rejected-uploads">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Blocked Emails</h3>
                <p class="text-3xl font-bold" id="metric-blocked-emails">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Rate Limit Violations</h3>
                <p class="text-3xl font-bold" id="metric-rate-limits">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Failed Authentications</h3>
                <p class="text-3xl font-bold" id="metric-auth-failures">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">CSP Violations</h3>
                <p class="text-3xl font-bold" id="metric-csp-violations">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Active IP Blocks</h3>
                <p class="text-3xl font-bold" id="metric-ip-blocks">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Malware Detected</h3>
                <p class="text-3xl font-bold text-red-600" id="metric-malware">-</p>
            </div>

            <div class="bg-white rounded-lg shadow p-6">
                <h3 class="text-sm font-semibold text-gray-500 mb-2">Attack Patterns</h3>
                <p class="text-3xl font-bold text-orange-600" id="metric-attack-patterns">-</p>
            </div>
        </div>

        <!-- Recent Security Events -->
        <div class="bg-white rounded-lg shadow p-6 mb-8">
            <h2 class="text-xl font-bold mb-4">Recent Security Events</h2>
            <div class="overflow-x-auto">
                <table class="min-w-full">
                    <thead class="bg-gray-50">
                        <tr>
                            <th class="px-4 py-2 text-left">Timestamp</th>
                            <th class="px-4 py-2 text-left">Event Type</th>
                            <th class="px-4 py-2 text-left">Source IP</th>
                            <th class="px-4 py-2 text-left">Severity</th>
                            <th class="px-4 py-2 text-left">Details</th>
                        </tr>
                    </thead>
                    <tbody id="events-table">
                        <tr>
                            <td colspan="5" class="px-4 py-8 text-center text-gray-500">
                                Loading events...
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>

        <!-- Top Violating IPs -->
        <div class="bg-white rounded-lg shadow p-6">
            <h2 class="text-xl font-bold mb-4">Top Violating IPs</h2>
            <div id="top-ips-list">
                <p class="text-gray-500">Loading...</p>
            </div>
        </div>
    </div>

    <script src="/js/admin/security-monitoring.js"></script>
</body>
</html>

Create Backend API (src/controllers/security-monitoring.controller.js):

const fs = require('fs').promises;
const readline = require('readline');
const { createReadStream } = require('fs');

const SECURITY_LOG_PATH = '/var/log/tractatus/security-audit.log';

/**
 * Parse security log within time range
 */
async function parseSecurityLog(timeRange) {
  const now = Date.now();
  const timeRanges = {
    '1h': 60 * 60 * 1000,
    '24h': 24 * 60 * 60 * 1000,
    '7d': 7 * 24 * 60 * 60 * 1000,
    '30d': 30 * 24 * 60 * 60 * 1000
  };

  const rangeMs = timeRanges[timeRange] || timeRanges['24h'];
  const cutoffTime = new Date(now - rangeMs).toISOString();

  const events = [];
  const fileStream = createReadStream(SECURITY_LOG_PATH);
  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });

  for await (const line of rl) {
    try {
      const event = JSON.parse(line);
      if (event.timestamp >= cutoffTime) {
        events.push(event);
      }
    } catch (error) {
      // Skip malformed lines
    }
  }

  return events;
}

/**
 * Calculate metrics from events
 */
function calculateMetrics(events) {
  const metrics = {
    rejected_uploads: 0,
    blocked_emails: 0,
    rate_limit_violations: 0,
    failed_authentications: 0,
    csp_violations: 0,
    active_ip_blocks: 0,
    malware_detected: 0,
    attack_patterns: 0
  };

  const ipViolations = {};

  for (const event of events) {
    switch (event.event_type) {
      case 'file_upload_rejected':
        metrics.rejected_uploads++;
        break;
      case 'malware_detected':
        metrics.malware_detected++;
        break;
      case 'email_blocked':
      case 'spam_filtered':
        metrics.blocked_emails++;
        break;
      case 'rate_limit_violation':
      case 'rate_limit_exceeded':
        metrics.rate_limit_violations++;
        break;
      case 'authentication_failure':
      case 'authorization_failure':
        metrics.failed_authentications++;
        break;
      case 'csp_violation':
        metrics.csp_violations++;
        break;
      case 'ip_blocked':
        metrics.active_ip_blocks++;
        break;
      case 'suspicious_patterns_detected':
      case 'xss_attempt':
      case 'nosql_injection_attempt':
        metrics.attack_patterns++;
        break;
    }

    // Track IP violations
    if (event.source_ip) {
      ipViolations[event.source_ip] = (ipViolations[event.source_ip] || 0) + 1;
    }
  }

  // Top 10 violating IPs
  const topIPs = Object.entries(ipViolations)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .map(([ip, count]) => ({ ip, violations: count }));

  return { metrics, topIPs };
}

/**
 * Get security metrics
 */
async function getSecurityMetrics(req, res) {
  try {
    const timeRange = req.query.timeRange || '24h';
    const events = await parseSecurityLog(timeRange);
    const { metrics, topIPs } = calculateMetrics(events);

    res.json({
      timeRange,
      metrics,
      topIPs,
      totalEvents: events.length
    });
  } catch (error) {
    console.error('[SECURITY METRICS ERROR]', error);
    res.status(500).json({
      error: 'Failed to retrieve security metrics'
    });
  }
}

/**
 * Get recent security events
 */
async function getRecentEvents(req, res) {
  try {
    const limit = parseInt(req.query.limit) || 50;
    const timeRange = req.query.timeRange || '24h';

    const events = await parseSecurityLog(timeRange);
    const recentEvents = events
      .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
      .slice(0, limit);

    res.json({
      events: recentEvents,
      total: events.length
    });
  } catch (error) {
    console.error('[RECENT EVENTS ERROR]', error);
    res.status(500).json({
      error: 'Failed to retrieve recent events'
    });
  }
}

module.exports = {
  getSecurityMetrics,
  getRecentEvents
};

Create Routes (src/routes/security-monitoring.routes.js):

const express = require('express');
const router = express.Router();
const { authenticateToken, requireRole } = require('../middleware/auth.middleware');
const { getSecurityMetrics, getRecentEvents } = require('../controllers/security-monitoring.controller');

// All security monitoring endpoints require admin role
router.use(authenticateToken);
router.use(requireRole('admin'));

router.get('/metrics', getSecurityMetrics);
router.get('/events', getRecentEvents);

module.exports = router;

5.2 fail2ban Integration

Create fail2ban Filter (/etc/fail2ban/filter.d/tractatus.conf):

# Fail2Ban filter for Tractatus security events

[Definition]

# Rate limit violations
failregex = "event_type":"rate_limit_violation".*"source_ip":"<HOST>"
            "event_type":"rate_limit_exceeded".*"source_ip":"<HOST>"
            "event_type":"authentication_failure".*"source_ip":"<HOST>"
            "event_type":"authorization_failure".*"source_ip":"<HOST>"
            "event_type":"nosql_injection_attempt".*"source_ip":"<HOST>"
            "event_type":"xss_attempt".*"source_ip":"<HOST>"
            "event_type":"malware_detected".*"source_ip":"<HOST>"
            "event_type":"suspicious_patterns_detected".*"source_ip":"<HOST>"

ignoreregex =

Configure fail2ban Jail (/etc/fail2ban/jail.local):

[tractatus]
enabled = true
port = http,https
filter = tractatus
logpath = /var/log/tractatus/security-audit.log
maxretry = 10
findtime = 3600
bantime = 86400
action = iptables-multiport[name=tractatus, port="http,https", protocol=tcp]

Test and Enable:

# Test filter
sudo fail2ban-regex /var/log/tractatus/security-audit.log /etc/fail2ban/filter.d/tractatus.conf

# Restart fail2ban
sudo systemctl restart fail2ban

# Check jail status
sudo fail2ban-client status tractatus

# View banned IPs
sudo fail2ban-client get tractatus banip --with-time

5.3 ProtonMail Alert System

Install ProtonMail Bridge (on server):

# Download ProtonMail Bridge
wget https://proton.me/download/bridge/protonmail-bridge_<version>_amd64.deb
sudo dpkg -i protonmail-bridge_<version>_amd64.deb

# Configure Bridge
protonmail-bridge --cli
# Follow prompts to login and configure SMTP/IMAP

Create Email Alert Utility (src/utils/email-alerts.js):

const nodemailer = require('nodemailer');

// ProtonMail Bridge SMTP configuration
const transporter = nodemailer.createTransport({
  host: '127.0.0.1',  // ProtonMail Bridge local SMTP
  port: 1025,
  secure: false,
  auth: {
    user: process.env.PROTON_EMAIL,  // security@tractatus.digital
    pass: process.env.PROTON_BRIDGE_PASSWORD
  }
});

/**
 * Send security alert via ProtonMail
 */
async function sendSecurityAlert(alert) {
  const {
    level,        // 'low', 'medium', 'high', 'critical'
    title,
    details,
    timestamp,
    recommendations
  } = alert;

  const severityColors = {
    low: '#3B82F6',      // blue
    medium: '#F59E0B',   // yellow
    high: '#EF4444',     // red
    critical: '#7F1D1D'  // dark red
  };

  const htmlContent = `
    <!DOCTYPE html>
    <html>
    <head>
        <style>
            body { font-family: Arial, sans-serif; line-height: 1.6; }
            .header { background: ${severityColors[level]}; color: white; padding: 20px; }
            .content { padding: 20px; }
            .detail-row { margin: 10px 0; }
            .label { font-weight: bold; }
            .footer { background: #f3f4f6; padding: 15px; margin-top: 20px; font-size: 0.9em; }
        </style>
    </head>
    <body>
        <div class="header">
            <h1>🚨 Security Alert: ${level.toUpperCase()}</h1>
        </div>
        <div class="content">
            <h2>${title}</h2>
            <div class="detail-row">
                <span class="label">Time:</span> ${timestamp}
            </div>
            <div class="detail-row">
                <span class="label">Severity:</span> ${level.toUpperCase()}
            </div>
            <h3>Details:</h3>
            <pre>${JSON.stringify(details, null, 2)}</pre>
            ${recommendations ? `
            <h3>Recommendations:</h3>
            <ul>
                ${recommendations.map(rec => `<li>${rec}</li>`).join('')}
            </ul>
            ` : ''}
        </div>
        <div class="footer">
            <p>This is an automated security alert from Tractatus.</p>
            <p>Review security dashboard: <a href="https://agenticgovernance.digital/admin/security-monitoring.html">Security Monitoring</a></p>
        </div>
    </body>
    </html>
  `;

  const recipients = [
    'security@tractatus.digital',
    ...(level === 'critical' ? ['admin@tractatus.digital'] : [])
  ];

  try {
    await transporter.sendMail({
      from: 'security@tractatus.digital',
      to: recipients.join(', '),
      subject: `[${level.toUpperCase()}] Security Alert: ${title}`,
      html: htmlContent
    });

    console.log(`[EMAIL ALERT] Sent ${level} alert: ${title}`);
  } catch (error) {
    console.error('[EMAIL ALERT ERROR]', error);
    // Fallback: log to console if email fails
    console.error('[ALERT]', alert);
  }
}

module.exports = { sendSecurityAlert };

5.4 Signal Notification Automation

Install signal-cli:

# Install signal-cli
wget https://github.com/AsamK/signal-cli/releases/download/v<version>/signal-cli-<version>.tar.gz
tar xf signal-cli-<version>.tar.gz -C /opt
sudo ln -sf /opt/signal-cli-<version>/bin/signal-cli /usr/local/bin/

# Register phone number
signal-cli -u +<phone-number> register

# Verify with code
signal-cli -u +<phone-number> verify <code>

# Test send
signal-cli -u +<phone-number> send -m "Test message" +<recipient-number>

Create Signal Alert Utility (src/utils/signal-alerts.js):

const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);

const SIGNAL_PHONE = process.env.SIGNAL_PHONE;  // Your Signal number
const SECURITY_GROUP = process.env.SIGNAL_SECURITY_GROUP_ID;  // Group chat ID

/**
 * Send Signal notification
 */
async function sendSignalNotification(message, level = 'medium') {
  const levelEmojis = {
    low: '',
    medium: '⚠️',
    high: '🚨',
    critical: '🔥'
  };

  const formattedMessage = `${levelEmojis[level]} TRACTATUS SECURITY ALERT\n\n${message}\n\nDashboard: https://agenticgovernance.digital/admin/security-monitoring.html`;

  try {
    // Send to group
    await execAsync(
      `signal-cli -u ${SIGNAL_PHONE} send -m "${formattedMessage}" -g ${SECURITY_GROUP}`
    );

    console.log(`[SIGNAL ALERT] Sent ${level} alert`);
  } catch (error) {
    console.error('[SIGNAL ALERT ERROR]', error);
  }
}

/**
 * Initiate Signal video call for critical incidents
 */
async function initiateEmergencyCall() {
  // Note: signal-cli doesn't support calls directly
  // Send urgent message prompting team to join video call
  const message = `🔴 CRITICAL SECURITY INCIDENT 🔴\n\nIMMEDIATE ACTION REQUIRED\n\nJOIN SIGNAL VIDEO CALL NOW\n\nInitiate call from mobile app.`;

  await sendSignalNotification(message, 'critical');
}

module.exports = {
  sendSignalNotification,
  initiateEmergencyCall
};

5.5 Alert Threshold Monitoring

Create Monitoring Service (src/services/alert-monitor.service.js):

const { parseSecurityLog, calculateMetrics } = require('../controllers/security-monitoring.controller');
const { sendSecurityAlert } = require('../utils/email-alerts');
const { sendSignalNotification } = require('../utils/signal-alerts');

// Alert thresholds (inst_046)
const THRESHOLDS = {
  single_ip_violations: {
    count: 10,
    window: 60 * 60 * 1000,  // 1 hour
    action: 'email'
  },
  global_violations: {
    count: 100,
    window: 60 * 60 * 1000,
    action: 'email_and_signal'
  },
  malware_detected: {
    count: 1,
    action: 'email_and_signal'
  },
  critical_events: [
    'malware_detected',
    'admin_account_compromise_attempt',
    'data_exfiltration_pattern'
  ]
};

/**
 * Check for alert conditions
 */
async function checkAlertConditions() {
  try {
    const events = await parseSecurityLog('1h');
    const { metrics, topIPs } = calculateMetrics(events);

    // Check single IP violations
    for (const { ip, violations } of topIPs) {
      if (violations >= THRESHOLDS.single_ip_violations.count) {
        await sendSecurityAlert({
          level: 'medium',
          title: `High violation count from single IP: ${ip}`,
          details: {
            ip,
            violations,
            threshold: THRESHOLDS.single_ip_violations.count,
            window: '1 hour'
          },
          timestamp: new Date().toISOString(),
          recommendations: [
            'Review recent events from this IP in security dashboard',
            'Consider manual IP block if attacks continue',
            'Check if IP is known scanner or legitimate user'
          ]
        });
      }
    }

    // Check global violations
    const totalViolations = metrics.rate_limit_violations +
                           metrics.attack_patterns +
                           metrics.failed_authentications;

    if (totalViolations >= THRESHOLDS.global_violations.count) {
      await sendSecurityAlert({
        level: 'high',
        title: 'Potential attack underway - high global violation rate',
        details: {
          total_violations: totalViolations,
          rate_limits: metrics.rate_limit_violations,
          attack_patterns: metrics.attack_patterns,
          auth_failures: metrics.failed_authentications,
          threshold: THRESHOLDS.global_violations.count,
          window: '1 hour'
        },
        timestamp: new Date().toISOString(),
        recommendations: [
          'Investigate attack patterns in security dashboard',
          'Check fail2ban status for automatic blocks',
          'Consider rate-limiting adjustments if legitimate traffic',
          'Monitor for data exfiltration attempts'
        ]
      });

      await sendSignalNotification(
        `⚠️ POTENTIAL ATTACK: ${totalViolations} security violations in last hour. Check dashboard immediately.`,
        'high'
      );
    }

    // Check malware detections
    if (metrics.malware_detected > 0) {
      await sendSecurityAlert({
        level: 'critical',
        title: `Malware detected: ${metrics.malware_detected} file(s) quarantined`,
        details: {
          malware_count: metrics.malware_detected,
          quarantine_location: '/var/quarantine/tractatus'
        },
        timestamp: new Date().toISOString(),
        recommendations: [
          'Review quarantined files immediately',
          'Identify source IPs and consider blocking',
          'Check if other uploads from same source',
          'Update ClamAV definitions if new threat'
        ]
      });

      await sendSignalNotification(
        `🚨 MALWARE DETECTED: ${metrics.malware_detected} file(s) quarantined. Review quarantine immediately.`,
        'critical'
      );
    }

    // Check for critical event types
    const criticalEvents = events.filter(e =>
      THRESHOLDS.critical_events.includes(e.event_type)
    );

    if (criticalEvents.length > 0) {
      for (const event of criticalEvents) {
        await sendSecurityAlert({
          level: 'critical',
          title: `Critical security event: ${event.event_type}`,
          details: event,
          timestamp: event.timestamp,
          recommendations: [
            'Immediate investigation required',
            'Review all activity from source IP',
            'Check for signs of compromise',
            'Consider incident response protocol'
          ]
        });

        await sendSignalNotification(
          `🔥 CRITICAL EVENT: ${event.event_type} from ${event.source_ip}. Immediate action required.`,
          'critical'
        );
      }
    }

  } catch (error) {
    console.error('[ALERT MONITOR ERROR]', error);
  }
}

/**
 * Start monitoring (run every 5 minutes)
 */
function startAlertMonitoring() {
  console.log('[ALERT MONITOR] Starting security alert monitoring...');

  // Initial check
  checkAlertConditions();

  // Check every 5 minutes
  setInterval(checkAlertConditions, 5 * 60 * 1000);
}

module.exports = {
  startAlertMonitoring,
  checkAlertConditions
};

Integrate with Server (src/server.js):

const { startAlertMonitoring } = require('./services/alert-monitor.service');

// Start alert monitoring after server starts
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
  startAlertMonitoring();
});

5.6 Weekly Security Reports

Create Report Generator (scripts/generate-security-report.js):

#!/usr/bin/env node

const fs = require('fs').promises;
const { parseSecurityLog, calculateMetrics } = require('../src/controllers/security-monitoring.controller');
const { sendSecurityAlert } = require('../src/utils/email-alerts');

/**
 * Generate weekly security report
 */
async function generateWeeklyReport() {
  const events = await parseSecurityLog('7d');
  const { metrics, topIPs } = calculateMetrics(events);

  // Calculate trends (compare with previous week)
  const lastWeekEvents = events.filter(e => {
    const eventTime = new Date(e.timestamp).getTime();
    const weekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
    const twoWeeksAgo = Date.now() - (14 * 24 * 60 * 60 * 1000);
    return eventTime >= twoWeeksAgo && eventTime < weekAgo;
  });
  const lastWeekMetrics = calculateMetrics(lastWeekEvents).metrics;

  const trends = {};
  for (const [key, value] of Object.entries(metrics)) {
    const lastWeekValue = lastWeekMetrics[key] || 0;
    const change = value - lastWeekValue;
    const percentChange = lastWeekValue > 0 ? (change / lastWeekValue) * 100 : 0;
    trends[key] = {
      current: value,
      previous: lastWeekValue,
      change,
      percent_change: percentChange.toFixed(1)
    };
  }

  // Identify attack patterns
  const attackPatterns = identifyAttackPatterns(events);

  // Generate recommendations
  const recommendations = generateRecommendations(metrics, trends, attackPatterns);

  // Format report
  const report = {
    title: 'Weekly Security Report',
    period: {
      start: new Date(Date.now() - (7 * 24 * 60 * 60 * 1000)).toISOString(),
      end: new Date().toISOString()
    },
    summary: {
      total_events: events.length,
      critical_events: events.filter(e => e.severity === 'high' || e.severity === 'critical').length,
      unique_ips: new Set(events.map(e => e.source_ip)).size
    },
    metrics,
    trends,
    top_violating_ips: topIPs.slice(0, 10),
    attack_patterns: attackPatterns,
    recommendations
  };

  // Save report to file
  const reportPath = `/var/log/tractatus/security-reports/report-${new Date().toISOString().split('T')[0]}.json`;
  await fs.mkdir('/var/log/tractatus/security-reports', { recursive: true });
  await fs.writeFile(reportPath, JSON.stringify(report, null, 2));

  // Send email report
  await sendSecurityAlert({
    level: 'low',
    title: 'Weekly Security Report',
    details: report,
    timestamp: new Date().toISOString(),
    recommendations
  });

  console.log('[WEEKLY REPORT] Generated and sent successfully');
}

/**
 * Identify attack patterns
 */
function identifyAttackPatterns(events) {
  const patterns = [];

  // Check for coordinated attacks (multiple IPs with similar patterns)
  const ipEventTypes = {};
  for (const event of events) {
    if (!ipEventTypes[event.source_ip]) {
      ipEventTypes[event.source_ip] = [];
    }
    ipEventTypes[event.source_ip].push(event.event_type);
  }

  const coordinatedIPs = Object.entries(ipEventTypes)
    .filter(([ip, types]) => types.length > 20)
    .map(([ip]) => ip);

  if (coordinatedIPs.length > 5) {
    patterns.push({
      type: 'coordinated_attack',
      description: `${coordinatedIPs.length} IPs showing coordinated attack patterns`,
      ips: coordinatedIPs.slice(0, 10)
    });
  }

  // Check for brute force attempts
  const authFailures = events.filter(e => e.event_type === 'authentication_failure');
  const authFailuresByIP = {};
  for (const event of authFailures) {
    authFailuresByIP[event.source_ip] = (authFailuresByIP[event.source_ip] || 0) + 1;
  }

  const bruteForceIPs = Object.entries(authFailuresByIP)
    .filter(([ip, count]) => count > 10)
    .map(([ip, count]) => ({ ip, attempts: count }));

  if (bruteForceIPs.length > 0) {
    patterns.push({
      type: 'brute_force_attack',
      description: `${bruteForceIPs.length} IPs attempting brute force authentication`,
      ips: bruteForceIPs.slice(0, 10)
    });
  }

  // Check for injection attempts
  const injectionAttempts = events.filter(e =>
    e.event_type === 'xss_attempt' || e.event_type === 'nosql_injection_attempt'
  );

  if (injectionAttempts.length > 10) {
    patterns.push({
      type: 'injection_attacks',
      description: `${injectionAttempts.length} injection attempts detected`,
      breakdown: {
        xss: injectionAttempts.filter(e => e.event_type === 'xss_attempt').length,
        nosql: injectionAttempts.filter(e => e.event_type === 'nosql_injection_attempt').length
      }
    });
  }

  return patterns;
}

/**
 * Generate recommendations
 */
function generateRecommendations(metrics, trends, attackPatterns) {
  const recommendations = [];

  // Malware detection recommendations
  if (metrics.malware_detected > 0) {
    recommendations.push({
      priority: 'high',
      category: 'malware',
      recommendation: `${metrics.malware_detected} malware detections this week. Ensure ClamAV definitions are up to date and review quarantine files.`
    });
  }

  // Rate limit recommendations
  if (trends.rate_limit_violations.percent_change > 50) {
    recommendations.push({
      priority: 'medium',
      category: 'rate_limiting',
      recommendation: `Rate limit violations increased by ${trends.rate_limit_violations.percent_change}%. Consider tightening rate limits or reviewing legitimate traffic patterns.`
    });
  }

  // Attack pattern recommendations
  for (const pattern of attackPatterns) {
    if (pattern.type === 'brute_force_attack') {
      recommendations.push({
        priority: 'high',
        category: 'authentication',
        recommendation: `Brute force attacks detected from ${pattern.ips.length} IPs. Consider implementing account lockout policies or CAPTCHA.`
      });
    }
  }

  // General security posture
  if (metrics.csp_violations > 0) {
    recommendations.push({
      priority: 'medium',
      category: 'csp',
      recommendation: `${metrics.csp_violations} CSP violations detected. Review and update Content Security Policy if needed.`
    });
  }

  return recommendations;
}

// Run if called directly
if (require.main === module) {
  generateWeeklyReport()
    .then(() => process.exit(0))
    .catch((error) => {
      console.error('[WEEKLY REPORT ERROR]', error);
      process.exit(1);
    });
}

module.exports = { generateWeeklyReport };

Schedule Weekly Reports (cron):

# Edit crontab
crontab -e

# Add weekly report (every Monday at 9am)
0 9 * * 1 /usr/bin/node /home/theflow/projects/tractatus/scripts/generate-security-report.js

Phase 5 Testing

Dashboard Access:

# Verify dashboard loads
curl -H "Authorization: Bearer <admin-token>" http://localhost:9000/admin/security-monitoring.html
# Expected: 200 OK, HTML content

# Test metrics endpoint
curl -H "Authorization: Bearer <admin-token>" http://localhost:9000/api/security-monitoring/metrics
# Expected: JSON with metrics

# Test events endpoint
curl -H "Authorization: Bearer <admin-token>" http://localhost:9000/api/security-monitoring/events?limit=10
# Expected: JSON with recent events

Alert Testing:

# Trigger single IP alert (10 violations)
for i in {1..15}; do
  curl http://localhost:9000/api/documents
done
# Expected: Email alert sent, Signal notification

# Trigger malware alert
curl -X POST -F "file=@eicar.txt" http://localhost:9000/api/cases/submit
# Expected: Email and Signal alert for malware detection

# Test weekly report generation
node scripts/generate-security-report.js
# Expected: Report generated, email sent

fail2ban Testing:

# Check fail2ban is monitoring log
sudo tail -f /var/log/fail2ban.log | grep tractatus

# Trigger ban
# (Perform actions that create 10+ violations in security log)

# Verify ban
sudo fail2ban-client status tractatus
# Expected: Banned IP list includes test IP

Phase 5 Success Criteria

  • Security dashboard accessible at /admin/security-monitoring.html
  • Real-time metrics displaying correctly
  • Recent events table populating from security log
  • Top violating IPs displayed
  • fail2ban jail operational and banning IPs
  • ProtonMail alerts sending successfully
  • Signal notifications delivering to group
  • Alert thresholds triggering correctly (10 violations = alert)
  • Weekly report generating and emailing
  • All security events logged consistently
  • Zero false positive alerts during testing

Phase 6: Integration, Hardening & Documentation

Duration: 1-2 weeks Effort: 25-35 hours Dependencies: Phase 1-5 complete Risk: Low

Objectives

  1. Integration testing across all security layers
  2. Penetration testing and vulnerability assessment
  3. Performance optimization
  4. Complete security documentation
  5. Incident response procedures
  6. Team training

Tasks

6.1 Integration Testing

Create Integration Test Suite (tests/integration/security-integration.test.js):

// Comprehensive tests for all security layers working together
// Test scenarios:
// - File upload with malware → quarantine → alert
// - XSS attempt → sanitization → rate limit → IP block
// - Authentication failure → logging → alert
// - Coordinated attack → multiple layers triggered → escalation

6.2 Penetration Testing

Automated Security Scanning:

# OWASP ZAP scan
zap-cli quick-scan --self-contained --start-options '-config api.disablekey=true' http://localhost:9000

# Nikto web scanner
nikto -h http://localhost:9000

# SSL/TLS testing
testssl.sh agenticgovernance.digital

# Port scanning
nmap -sV -sC agenticgovernance.digital

Manual Testing Checklist:

  • SQL/NoSQL injection attempts on all form fields
  • XSS payloads in all input fields
  • CSRF token bypass attempts
  • File upload malware variants (ZIP bombs, polyglot files)
  • Authentication bypass attempts
  • Authorization escalation attempts
  • Rate limit bypass techniques
  • CSP bypass attempts
  • Email attachment exploits
  • API endpoint enumeration
  • Session hijacking attempts

6.3 Performance Optimization

Redis Performance:

# Monitor Redis performance
redis-cli --latency
redis-cli --stat

# Optimize memory
redis-cli CONFIG SET maxmemory 256mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

ClamAV Performance:

# Use daemon scanning (clamdscan) instead of clamscan
# Already configured in file-security.middleware.js

# Increase max threads
# Edit /etc/clamav/clamd.conf
# MaxThreads 4

Log Rotation Verification:

# Force log rotation
sudo logrotate -f /etc/logrotate.d/tractatus

# Verify rotation working
ls -lh /var/log/tractatus/

6.4 Complete Security Documentation

Create/Update Security Docs:

  1. docs/security/SECURITY_POLICY.md:

    • Security framework overview
    • All 6 layers documented
    • Tool inventory with versions
    • Configuration standards
    • Update procedures
  2. docs/security/INCIDENT_RESPONSE.md:

    • Incident classification levels
    • Response procedures for each level
    • Communication protocols (ProtonMail, Signal)
    • Escalation paths
    • Post-incident review process
  3. docs/security/ALERT_THRESHOLDS.md:

    • All alert thresholds documented
    • Rationale for each threshold
    • Adjustment procedures
    • False positive handling
    • Alert fatigue mitigation
  4. docs/security/TOOL_INVENTORY.md:

    • Complete list of sovereign tools
    • Version numbers
    • Update schedules
    • Configuration files
    • Maintenance procedures
  5. docs/security/SECURITY_TESTING.md:

    • Testing procedures
    • Penetration testing schedule
    • Vulnerability disclosure policy
    • Bug bounty program (if applicable)

6.5 Incident Response Procedures

Create Incident Response Playbook (docs/security/INCIDENT_RESPONSE.md):

# Incident Response Playbook

## Incident Classification

### Level 1: Low Severity
- Single malware detection (quarantined)
- Low volume rate limit violations
- Individual failed authentication attempts
**Response Time:** Within 24 hours
**Communication:** Email to security@tractatus.digital

### Level 2: Medium Severity
- High violation count from single IP (>10 in 1 hour)
- Multiple quarantined files
- Moderate attack patterns detected
**Response Time:** Within 4 hours
**Communication:** Email + Signal notification

### Level 3: High Severity
- Potential coordinated attack (>100 violations in 1 hour)
- Authentication bypass attempts
- Authorization escalation attempts
**Response Time:** Within 1 hour
**Communication:** Email + Signal notification + Phone call

### Level 4: Critical
- Confirmed system compromise
- Data breach/exfiltration
- Admin account compromise
- Active malware infection
**Response Time:** Immediate
**Communication:** Signal emergency notification + Video call

## Response Procedures

### Level 1 Response
1. Review security dashboard
2. Verify quarantine integrity
3. Check fail2ban for automatic blocks
4. Document findings
5. Update security report

### Level 2 Response
1. All Level 1 steps
2. Manual investigation of source IP
3. Consider manual IP block if automated block insufficient
4. Review last 24 hours of activity from source
5. Update YARA rules if new pattern detected
6. Notify team via Signal

### Level 3 Response
1. All Level 2 steps
2. Convene security team via Signal
3. Real-time monitoring of ongoing attack
4. Consider temporary rate limit tightening
5. Prepare incident report
6. Consider external notification if customer data affected

### Level 4 Response
1. **IMMEDIATE ACTION**
2. Initiate Signal video call with all security team
3. Isolate affected systems if possible
4. Capture forensic evidence (logs, memory dumps)
5. Engage external incident response team if needed
6. Follow data breach notification procedures
7. Preserve evidence for investigation
8. Full system audit and remediation
9. Post-incident review and lessons learned

## Post-Incident Procedures

After any incident Level 2 or above:
1. Complete incident report within 48 hours
2. Conduct post-incident review meeting
3. Document lessons learned
4. Update security rules/thresholds if needed
5. Implement preventive measures
6. Update this playbook if procedures changed

## Contact Information

**Security Team:**
- Email: security@tractatus.digital
- Signal Group: Tractatus Security Team
- Emergency Phone: [REDACTED]

**External Resources:**
- Incident Response Consultant: [Contact info]
- Legal Counsel: [Contact info]
- Law Enforcement Cyber Unit: [Contact info]

6.6 Team Training

Security Training Modules:

  1. Security Framework Overview (1 hour)

    • All 6 layers explained
    • Tool demonstrations
    • Dashboard walkthrough
  2. Incident Response Training (2 hours)

    • Playbook review
    • Simulation exercises
    • Communication protocols
  3. Tool-Specific Training (3 hours)

    • ClamAV and YARA
    • fail2ban management
    • Redis monitoring
    • ProtonMail and Signal
  4. Security Monitoring (1 hour)

    • Dashboard usage
    • Log analysis
    • Alert triage

Training Schedule:

  • Initial training: All modules in first week of Phase 6
  • Refresher training: Quarterly
  • Incident response drill: Bi-annually

Phase 6 Success Criteria

  • All integration tests passing
  • Penetration testing completed with no critical vulnerabilities
  • Performance benchmarks met (response times within acceptable ranges)
  • All security documentation complete and reviewed
  • Incident response playbook tested with simulation
  • Team training completed for all members
  • External security audit passed (if applicable)
  • Production deployment approval obtained
  • Post-implementation review completed

Post-Implementation Maintenance

Daily Tasks

  • Review security dashboard metrics
  • Check fail2ban status and banned IPs
  • Monitor alert emails for critical events
  • Verify ClamAV daemon running

Weekly Tasks

  • Review weekly security report
  • Analyze attack patterns and trends
  • Review quarantined files
  • Update YARA rules if needed
  • Test backup and restore procedures

Monthly Tasks

  • Update ClamAV definitions (automatic, verify)
  • Review and update fail2ban rules
  • Analyze false positive rate
  • Review and adjust alert thresholds
  • Security tool version updates
  • Review access control lists
  • Audit user accounts and permissions

Quarterly Tasks

  • Comprehensive security audit
  • Penetration testing
  • Review and update security documentation
  • Team security training refresher
  • Review incident response playbook
  • Test disaster recovery procedures

Annual Tasks

  • External security audit
  • Rotate JWT secrets
  • Review and renew SSL certificates
  • Comprehensive security posture assessment
  • Update security policies
  • Review and update threat model

Timeline Summary

Phase Duration Dependencies Deliverables
Phase 1: Foundation 1-2 weeks None All sovereign tools installed, logging infrastructure, communication channels
Phase 2: File & Email Security 2-3 weeks Phase 1 File upload validation, email security stack, quarantine system
Phase 3: Application Security 1-2 weeks Phase 1 Input validation, HTTP headers, CSRF protection, CSP reporting
Phase 4: API Protection 1-2 weeks Phase 1 (Redis) Rate limiting, JWT auth, IP blocking, request validation
Phase 5: Monitoring & Alerting 2-3 weeks Phases 1-4 Dashboard, fail2ban, ProtonMail alerts, Signal notifications, weekly reports
Phase 6: Integration & Hardening 1-2 weeks Phases 1-5 Integration tests, penetration testing, documentation, training
TOTAL 8-14 weeks Complete security framework operational

Resource Requirements

Personnel

  • System Administrator: 60-80 hours
  • Developer: 120-150 hours
  • Security Reviewer: 40-50 hours
  • Project Owner: 20-30 hours

Infrastructure

  • Production Server: Ubuntu 22.04 LTS with adequate resources (8GB RAM minimum)
  • Redis: 256MB memory allocation
  • Storage: 50GB minimum for logs (90-day retention)
  • Bandwidth: Standard production bandwidth

Tools & Services

  • Sovereign Tools: Free (open-source)
  • Proton Business Account: ~€8/month per user
  • Signal: Free
  • SSL Certificate: Free (Let's Encrypt) or purchased
  • Domain: Existing (agenticgovernance.digital)

Training

  • Internal Training: 7 hours per team member
  • External Consultation: Optional, estimate €2,000-5,000 for security audit

Risk Mitigation

Technical Risks

Risk Likelihood Impact Mitigation
ClamAV false positives Medium Medium Whitelist mechanism, quarantine review process
Redis failure Low High Fall-back to in-memory rate limiting, Redis monitoring
Performance degradation Medium Medium Performance testing, optimization in Phase 6
Log storage overflow Low Medium Automated rotation, monitoring, compression

Operational Risks

Risk Likelihood Impact Mitigation
Alert fatigue Medium High Careful threshold tuning, false positive tracking
Team unavailability Low High Documented procedures, automated responses, on-call rotation
Tool incompatibility Low Medium Thorough testing in Phase 1, version documentation

Security Risks

Risk Likelihood Impact Mitigation
Zero-day exploits Low High Defense in depth, rapid patching, monitoring
Sophisticated attacks Medium High Multiple security layers, expert consultation available
Insider threats Very Low Critical Access controls, audit logging, separation of duties

Success Metrics

Technical Metrics

  • Malware Detection Rate: >99% (tested with known malware samples)
  • False Positive Rate: <1% for file uploads
  • API Response Time Impact: <50ms additional latency
  • Dashboard Load Time: <2 seconds
  • Alert Delivery Time: <5 minutes from event

Security Metrics

  • Blocked Attacks: Tracked and trending downward over time
  • Mean Time to Detect (MTTD): <15 minutes for high-severity incidents
  • Mean Time to Respond (MTTR): <1 hour for high-severity incidents
  • Security Test Pass Rate: 100% for penetration testing

Operational Metrics

  • System Uptime: >99.9%
  • Security Tool Availability: >99.5%
  • Team Training Completion: 100%
  • Documentation Completeness: 100%

Approval & Sign-Off

Prepared By: Claude (Tractatus Development Team) Date: 2025-10-14 Version: 1.0

Reviewed By:

  • System Administrator: _______________ Date: _______
  • Lead Developer: _______________ Date: _______
  • Security Reviewer: _______________ Date: _______
  • Project Owner: _______________ Date: _______

Approved for Implementation:

  • Project Owner: _______________ Date: _______

END OF SECURITY IMPLEMENTATION ROADMAP

This document should be reviewed and updated after each phase completion and whenever security requirements change.