← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-security/security-agent.ts

734 lines

import cookieParser from "cookie-parser";
/**
 * DW-Agents: Security Agent
 *
 * Server security monitoring and management system:
 * - System security status monitoring
 * - Security updates detection
 * - Firewall status monitoring
 * - Failed login attempt tracking
 * - SSL certificate monitoring
 * - Port scanning detection
 * - Automatic security issue reporting to Needs Attention agent
 * - Real-time security dashboard
 *
 * Port: 9892
 */

import Anthropic from '@anthropic-ai/sdk';
import express, { Request, Response } from 'express';
import { requireGlobalAuth } from '../shared-global-auth';
import session from 'express-session';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import fetch from 'node-fetch';

import { chatMiddleware } from '../shared-chat-integration';

const execAsync = promisify(exec);

const app = express();

// Global Authentication
const PORT = 9892;

// Global Authentication - MUST come before any other middleware
app.use(cookieParser());

// Add inter-agent chat widget (after auth)
app.use(chatMiddleware({
  agentId: 'agent-security',
  agentName: 'Security',
  port: 9892,
  category: 'agent'
}));


// Authentication

// Anthropic AI client
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY || "${ANTHROPIC_API_KEY}",
});

// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Session configuration
declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant', content: string }>;
  }
}

app.use(
  session({
    secret: 'dw-agents-security-2025',
    resave: false,
    saveUninitialized: true,
    rolling: true,
    cookie: {
      secure: false,
      httpOnly: true,
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    },
  })
);

// Authentication handled by requireGlobalAuth from shared module

// Security status tracking
interface SecurityIssue {
  id: string;
  timestamp: string;
  severity: 'critical' | 'high' | 'medium' | 'low';
  category: string;
  title: string;
  description: string;
  recommendation: string;
  resolved: boolean;
  reportedToUrgent: boolean;
}

interface SecurityStats {
  totalIssues: number;
  criticalIssues: number;
  highIssues: number;
  mediumIssues: number;
  lowIssues: number;
  resolvedIssues: number;
  lastScan: Date;
  systemUptime: string;
  failedLogins: number;
  activeConnections: number;
}

let securityIssues: SecurityIssue[] = [];
let stats: SecurityStats = {
  totalIssues: 0,
  criticalIssues: 0,
  highIssues: 0,
  mediumIssues: 0,
  lowIssues: 0,
  resolvedIssues: 0,
  lastScan: new Date(),
  systemUptime: '0 days',
  failedLogins: 0,
  activeConnections: 0,
};

// Load/save issues
const ISSUES_FILE = path.join(__dirname, 'security-issues.json');

function loadIssues() {
  if (fs.existsSync(ISSUES_FILE)) {
    try {
      securityIssues = JSON.parse(fs.readFileSync(ISSUES_FILE, 'utf-8'));
      updateStats();
    } catch (e) {
      console.error('Error loading issues:', e);
    }
  }
}

function saveIssues() {
  fs.writeFileSync(ISSUES_FILE, JSON.stringify(securityIssues, null, 2));
}

function updateStats() {
  stats.totalIssues = securityIssues.length;
  stats.criticalIssues = securityIssues.filter(i => i.severity === 'critical' && !i.resolved).length;
  stats.highIssues = securityIssues.filter(i => i.severity === 'high' && !i.resolved).length;
  stats.mediumIssues = securityIssues.filter(i => i.severity === 'medium' && !i.resolved).length;
  stats.lowIssues = securityIssues.filter(i => i.severity === 'low' && !i.resolved).length;
  stats.resolvedIssues = securityIssues.filter(i => i.resolved).length;
}

loadIssues();

// Report critical issues to Needs Attention agent
async function reportToUrgent(issue: SecurityIssue) {
  try {
    const response = await fetch('http://45.61.58.125:9886/api/add-urgent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        title: `🔒 Security: ${issue.title}`,
        description: issue.description,
        severity: issue.severity,
        category: 'security',
        source: 'Security Agent',
      }),
    });

    if (response.ok) {
      issue.reportedToUrgent = true;
      saveIssues();
      console.log(`✅ Reported issue ${issue.id} to Needs Attention agent`);
    }
  } catch (error) {
    console.error('Error reporting to Urgent agent:', error);
  }
}

// Security check functions
async function checkSystemUpdates(): Promise<SecurityIssue | null> {
  try {
    const { stdout } = await execAsync('apt list --upgradable 2>/dev/null | grep -i security | wc -l');
    const securityUpdates = parseInt(stdout.trim());

    if (securityUpdates > 0) {
      return {
        id: `sec-updates-${Date.now()}`,
        timestamp: new Date().toISOString(),
        severity: securityUpdates > 10 ? 'high' : 'medium',
        category: 'updates',
        title: `${securityUpdates} Security Updates Available`,
        description: `There are ${securityUpdates} security updates available for the system.`,
        recommendation: 'Run: sudo apt update && sudo apt upgrade',
        resolved: false,
        reportedToUrgent: false,
      };
    }
  } catch (error) {
    console.error('Error checking updates:', error);
  }
  return null;
}

async function checkFirewallStatus(): Promise<SecurityIssue | null> {
  try {
    const { stdout } = await execAsync('sudo ufw status 2>/dev/null || echo "inactive"');
    if (stdout.toLowerCase().includes('inactive')) {
      return {
        id: `firewall-${Date.now()}`,
        timestamp: new Date().toISOString(),
        severity: 'high',
        category: 'firewall',
        title: 'Firewall is Inactive',
        description: 'The UFW firewall is not active, leaving the system exposed.',
        recommendation: 'Enable firewall: sudo ufw enable',
        resolved: false,
        reportedToUrgent: false,
      };
    }
  } catch (error) {
    // Firewall might not be installed
  }
  return null;
}

async function checkFailedLogins(): Promise<SecurityIssue | null> {
  try {
    const { stdout } = await execAsync('grep "Failed password" /var/log/auth.log 2>/dev/null | tail -100 | wc -l');
    const failedLogins = parseInt(stdout.trim());
    stats.failedLogins = failedLogins;

    if (failedLogins > 50) {
      return {
        id: `failed-logins-${Date.now()}`,
        timestamp: new Date().toISOString(),
        severity: failedLogins > 100 ? 'critical' : 'high',
        category: 'authentication',
        title: `${failedLogins} Recent Failed Login Attempts`,
        description: `Detected ${failedLogins} failed login attempts in recent logs. Possible brute force attack.`,
        recommendation: 'Review /var/log/auth.log and consider implementing fail2ban',
        resolved: false,
        reportedToUrgent: false,
      };
    }
  } catch (error) {
    // Auth log might not be accessible
  }
  return null;
}

async function checkSSLCertificates(): Promise<SecurityIssue | null> {
  try {
    const { stdout } = await execAsync('sudo certbot certificates 2>/dev/null | grep "Expiry Date" | head -1');
    if (stdout) {
      // Parse expiry date and check if within 30 days
      const match = stdout.match(/(\d{4}-\d{2}-\d{2})/);
      if (match) {
        const expiryDate = new Date(match[1]);
        const daysUntilExpiry = Math.floor((expiryDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24));

        if (daysUntilExpiry < 30) {
          return {
            id: `ssl-expiry-${Date.now()}`,
            timestamp: new Date().toISOString(),
            severity: daysUntilExpiry < 7 ? 'critical' : 'high',
            category: 'ssl',
            title: `SSL Certificate Expiring in ${daysUntilExpiry} Days`,
            description: `SSL certificate will expire on ${expiryDate.toDateString()}`,
            recommendation: 'Renew certificate: sudo certbot renew',
            resolved: false,
            reportedToUrgent: false,
          };
        }
      }
    }
  } catch (error) {
    // Certbot might not be installed
  }
  return null;
}

async function checkOpenPorts(): Promise<SecurityIssue | null> {
  try {
    const { stdout } = await execAsync('ss -tuln | grep LISTEN | wc -l');
    const openPorts = parseInt(stdout.trim());
    stats.activeConnections = openPorts;

    // Check for commonly exploited ports
    const { stdout: dangerousPorts } = await execAsync('ss -tuln | grep -E ":(23|21|445|3389|5900)" | wc -l');
    const dangerous = parseInt(dangerousPorts.trim());

    if (dangerous > 0) {
      return {
        id: `dangerous-ports-${Date.now()}`,
        timestamp: new Date().toISOString(),
        severity: 'critical',
        category: 'network',
        title: 'Dangerous Ports Open',
        description: `Detected ${dangerous} commonly exploited ports open (Telnet, FTP, SMB, RDP, VNC)`,
        recommendation: 'Close unnecessary ports and use secure alternatives',
        resolved: false,
        reportedToUrgent: false,
      };
    }
  } catch (error) {
    console.error('Error checking ports:', error);
  }
  return null;
}

async function checkSystemUptime() {
  try {
    const { stdout } = await execAsync('uptime -p');
    stats.systemUptime = stdout.trim().replace('up ', '');
  } catch (error) {
    console.error('Error checking uptime:', error);
  }
}

// Run security scan
async function runSecurityScan() {
  console.log('🔍 Running security scan...');

  const checks = [
    checkSystemUpdates(),
    checkFirewallStatus(),
    checkFailedLogins(),
    checkSSLCertificates(),
    checkOpenPorts(),
  ];

  const results = await Promise.all(checks);
  await checkSystemUptime();

  for (const issue of results) {
    if (issue) {
      // Check if this issue already exists
      const existing = securityIssues.find(i => i.category === issue.category && !i.resolved);
      if (!existing) {
        securityIssues.push(issue);
        console.log(`⚠️  New security issue: ${issue.title}`);

        // Report critical and high severity issues to Needs Attention
        if (issue.severity === 'critical' || issue.severity === 'high') {
          await reportToUrgent(issue);
        }
      }
    }
  }

  stats.lastScan = new Date();
  updateStats();
  saveIssues();

  console.log(`✅ Security scan complete. Found ${stats.criticalIssues + stats.highIssues + stats.mediumIssues + stats.lowIssues} active issues.`);
}

// Run initial scan and schedule periodic scans
runSecurityScan();
setInterval(runSecurityScan, 60 * 60 * 1000); // Every hour

// Login/logout handled by shared-global-auth module

// Main dashboard
app.get('/', requireGlobalAuth, (req, res) => {
  const unresolvedIssues = securityIssues.filter(i => !i.resolved).sort((a, b) => {
    const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    return severityOrder[a.severity] - severityOrder[b.severity];
  });

  res.send(`
<!DOCTYPE html>
<html>
<head>
  <title>Security Agent - DW-Agents</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
      background: linear-gradient(135deg, #DC143C 0%, #8B0000 100%);
      min-height: 100vh;
      padding: 30px;
    }
    .container {
      max-width: 1400px;
      margin: 0 auto;
    }
    .header {
      background: white;
      padding: 40px;
      border-radius: 20px;
      margin-bottom: 30px;
      box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .header h1 {
      color: #DC143C;
      font-size: 2.5em;
      margin-bottom: 10px;
    }
    .header .subtitle {
      color: #666;
      font-size: 1.2em;
    }
    .stats-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
      gap: 20px;
      margin-bottom: 30px;
    }
    .stat-card {
      background: white;
      padding: 30px;
      border-radius: 15px;
      box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
      text-align: center;
    }
    .stat-card .icon {
      font-size: 3em;
      margin-bottom: 15px;
    }
    .stat-card .value {
      font-size: 2.5em;
      font-weight: bold;
      color: #333;
      margin-bottom: 10px;
    }
    .stat-card .label {
      color: #666;
      font-size: 1.1em;
    }
    .stat-card.critical { border-left: 6px solid #DC143C; }
    .stat-card.high { border-left: 6px solid #FF6B35; }
    .stat-card.medium { border-left: 6px solid #FFA500; }
    .stat-card.low { border-left: 6px solid #4CAF50; }
    .section {
      background: white;
      padding: 30px;
      border-radius: 20px;
      margin-bottom: 30px;
      box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
    }
    .section h2 {
      color: #DC143C;
      margin-bottom: 20px;
      font-size: 1.8em;
    }
    .issue-card {
      background: #f8f9fa;
      padding: 20px;
      border-radius: 10px;
      margin-bottom: 15px;
      border-left: 6px solid;
    }
    .issue-card.critical { border-left-color: #DC143C; }
    .issue-card.high { border-left-color: #FF6B35; }
    .issue-card.medium { border-left-color: #FFA500; }
    .issue-card.low { border-left-color: #4CAF50; }
    .issue-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .issue-title {
      font-weight: 600;
      font-size: 1.2em;
      color: #333;
    }
    .severity-badge {
      padding: 5px 15px;
      border-radius: 20px;
      font-size: 0.85em;
      font-weight: 600;
      text-transform: uppercase;
    }
    .severity-badge.critical {
      background: #fee;
      color: #DC143C;
    }
    .severity-badge.high {
      background: #fff3e0;
      color: #FF6B35;
    }
    .severity-badge.medium {
      background: #fff8e1;
      color: #FFA500;
    }
    .severity-badge.low {
      background: #e8f5e9;
      color: #4CAF50;
    }
    .issue-description {
      color: #666;
      margin-bottom: 10px;
      line-height: 1.6;
    }
    .issue-recommendation {
      background: white;
      padding: 10px;
      border-radius: 5px;
      font-family: 'Courier New', monospace;
      font-size: 0.9em;
      color: #333;
    }
    .btn {
      padding: 12px 30px;
      border: none;
      border-radius: 10px;
      font-size: 1em;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s;
      text-decoration: none;
      display: inline-block;
    }
    .btn-primary {
      background: linear-gradient(135deg, #DC143C 0%, #8B0000 100%);
      color: white;
    }
    .btn-primary:hover {
      transform: translateY(-2px);
      box-shadow: 0 5px 15px rgba(220, 20, 60, 0.3);
    }
    .btn-secondary {
      background: #f0f0f0;
      color: #333;
    }
    .btn-secondary:hover {
      background: #e0e0e0;
    }
    .btn-success {
      background: #4CAF50;
      color: white;
    }
    .no-issues {
      text-align: center;
      padding: 40px;
      color: #666;
      font-size: 1.2em;
    }
    .no-issues .icon {
      font-size: 5em;
      margin-bottom: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="header">
      <div>
        <h1>🔒 Security Agent</h1>
        <div class="subtitle">System Security Monitoring</div>
        <div style="margin-top: 10px; color: #888;">Last scan: ${stats.lastScan.toLocaleString()}</div>
      </div>
      <div>
        <a href="/logout" class="btn btn-secondary">Logout</a>
        <button onclick="runScan()" class="btn btn-primary" style="margin-left: 10px;">🔍 Run Scan Now</button>
      </div>
    </div>

    <div class="stats-grid">
      <div class="stat-card critical">
        <div class="icon">🚨</div>
        <div class="value">${stats.criticalIssues}</div>
        <div class="label">Critical Issues</div>
      </div>
      <div class="stat-card high">
        <div class="icon">⚠️</div>
        <div class="value">${stats.highIssues}</div>
        <div class="label">High Priority</div>
      </div>
      <div class="stat-card medium">
        <div class="icon">📋</div>
        <div class="value">${stats.mediumIssues}</div>
        <div class="label">Medium Priority</div>
      </div>
      <div class="stat-card low">
        <div class="icon">ℹ️</div>
        <div class="value">${stats.lowIssues}</div>
        <div class="label">Low Priority</div>
      </div>
      <div class="stat-card">
        <div class="icon">⏱️</div>
        <div class="value" style="font-size: 1.5em;">${stats.systemUptime}</div>
        <div class="label">System Uptime</div>
      </div>
      <div class="stat-card">
        <div class="icon">🔌</div>
        <div class="value">${stats.activeConnections}</div>
        <div class="label">Open Ports</div>
      </div>
    </div>

    <div class="section">
      <h2>⚠️ Active Security Issues</h2>
      ${unresolvedIssues.length === 0 ? `
        <div class="no-issues">
          <div class="icon">✅</div>
          <div>No active security issues detected!</div>
          <div style="font-size: 0.9em; margin-top: 10px;">System is secure and up to date.</div>
        </div>
      ` : unresolvedIssues.map(issue => `
        <div class="issue-card ${issue.severity}">
          <div class="issue-header">
            <div class="issue-title">${issue.title}</div>
            <span class="severity-badge ${issue.severity}">${issue.severity}</span>
          </div>
          <div class="issue-description">${issue.description}</div>
          <div class="issue-recommendation">
            <strong>💡 Recommendation:</strong> ${issue.recommendation}
          </div>
          <div style="margin-top: 15px;">
            <button onclick="resolveIssue('${issue.id}')" class="btn btn-success">Mark as Resolved</button>
            ${!issue.reportedToUrgent && (issue.severity === 'critical' || issue.severity === 'high') ? `
              <button onclick="reportToUrgent('${issue.id}')" class="btn btn-primary" style="margin-left: 10px;">
                📢 Report to Urgent
              </button>
            ` : ''}
          </div>
        </div>
      `).join('')}
    </div>
  </div>

  <script>
    async function runScan() {
      try {
        const response = await fetch('/api/scan', { method: 'POST' });
        if (response.ok) {
          alert('Security scan initiated. Page will reload.');
          location.reload();
        }
      } catch (error) {
        alert('Error running scan');
      }
    }

    async function resolveIssue(id) {
      try {
        const response = await fetch(\`/api/issues/\${id}/resolve\`, { method: 'POST' });
        if (response.ok) {
          location.reload();
        }
      } catch (error) {
        alert('Error resolving issue');
      }
    }

    async function reportToUrgent(id) {
      try {
        const response = await fetch(\`/api/issues/\${id}/report\`, { method: 'POST' });
        if (response.ok) {
          alert('Issue reported to Needs Attention agent');
          location.reload();
        }
      } catch (error) {
        alert('Error reporting issue');
      }
    }

    // Auto-refresh every 5 minutes
    setTimeout(() => location.reload(), 5 * 60 * 1000);
  </script>
</body>
</html>
  `);
});

// API endpoints
app.post('/api/scan', requireGlobalAuth, async (req, res) => {
  runSecurityScan();
  res.json({ success: true, message: 'Security scan initiated' });
});

app.post('/api/issues/:id/resolve', requireGlobalAuth, (req, res) => {
  const issue = securityIssues.find(i => i.id === req.params.id);
  if (issue) {
    issue.resolved = true;
    updateStats();
    saveIssues();
    res.json({ success: true });
  } else {
    res.status(404).json({ error: 'Issue not found' });
  }
});

app.post('/api/issues/:id/report', requireGlobalAuth, async (req, res) => {
  const issue = securityIssues.find(i => i.id === req.params.id);
  if (issue) {
    await reportToUrgent(issue);
    res.json({ success: true });
  } else {
    res.status(404).json({ error: 'Issue not found' });
  }
});

app.get('/api/stats', requireGlobalAuth, (req, res) => {
  res.json(stats);
});

app.get('/api/issues', requireGlobalAuth, (req, res) => {
  res.json(securityIssues.filter(i => !i.resolved));
});


// Metrics endpoint for daily reporting
app.get("/api/metrics", (req: Request, res: Response) => {
  res.json({
    status: "online",
    uptime: process.uptime(),
    responseTime: 0,
    tasksCompleted: 0,
    errorsToday: 0,
    lastActivity: new Date().toISOString(),
    qnaReadiness: 100
  });
});

app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('🔒 DW-Agents: Security Agent');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log('🌍 External: http://45.61.58.125:' + PORT);
  console.log('🏠 Local: http://localhost:' + PORT);
  console.log('');
  console.log('✅ Security monitoring active');
  console.log('📊 Running security scans every hour');
  console.log('🚨 Auto-reporting critical/high issues to Needs Attention agent');
  console.log('');
});