- 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>
3537 lines
97 KiB
Markdown
3537 lines
97 KiB
Markdown
# 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
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# Install YARA
|
||
sudo apt install -y yara
|
||
|
||
# Verify installation
|
||
yara --version
|
||
```
|
||
|
||
**Create YARA Rules Directory:**
|
||
```bash
|
||
sudo mkdir -p /etc/yara/rules
|
||
sudo mkdir -p /var/log/yara
|
||
```
|
||
|
||
**Create Base Rule Set** (`/etc/yara/rules/tractatus-base.yar`):
|
||
```yara
|
||
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:**
|
||
```bash
|
||
# Test YARA rules
|
||
yara /etc/yara/rules/tractatus-base.yar /path/to/test/files/
|
||
```
|
||
|
||
#### 1.3 fail2ban Installation
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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)
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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
|
||
```bash
|
||
# 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`:**
|
||
```javascript
|
||
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`:**
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
npm install dompurify validator jsdom csurf express-rate-limit
|
||
```
|
||
|
||
**Create `src/middleware/input-validation.middleware.js`:**
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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):**
|
||
```bash
|
||
npm install helmet
|
||
```
|
||
|
||
**Create `src/middleware/security-headers.middleware.js`:**
|
||
```javascript
|
||
/**
|
||
* 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`):
|
||
```javascript
|
||
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:**
|
||
```bash
|
||
npm install csurf cookie-parser
|
||
```
|
||
|
||
**Configure CSRF** (`src/server.js`):
|
||
```javascript
|
||
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:**
|
||
```javascript
|
||
// Endpoint to get CSRF token
|
||
app.get('/api/csrf-token', (req, res) => {
|
||
res.json({ csrfToken: req.csrfToken() });
|
||
});
|
||
```
|
||
|
||
**Client-side Integration:**
|
||
```javascript
|
||
// 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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
const securityRoutes = require('./routes/security.routes');
|
||
app.use('/api', securityRoutes);
|
||
```
|
||
|
||
### Phase 3 Testing
|
||
|
||
**Input Validation Tests:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
npm install jsonwebtoken bcryptjs
|
||
```
|
||
|
||
**Create `src/middleware/auth.middleware.js`:**
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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`:**
|
||
```javascript
|
||
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`:**
|
||
```javascript
|
||
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`:**
|
||
```javascript
|
||
/**
|
||
* 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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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`):
|
||
```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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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`):
|
||
```ini
|
||
# 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`):
|
||
```ini
|
||
[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:**
|
||
```bash
|
||
# 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):**
|
||
```bash
|
||
# 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`):
|
||
```javascript
|
||
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:**
|
||
```bash
|
||
# 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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
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`):
|
||
```javascript
|
||
#!/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):
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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`):
|
||
```javascript
|
||
// 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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:**
|
||
```bash
|
||
# 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`):
|
||
|
||
```markdown
|
||
# 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.*
|