Files
shopify-ai-backup/SECURITY_REMEDIATION_PLAN.md
southseact-3d 1fbf5abce6 feat: Add support for multiple AI providers (bytez, llm7.io, aimlapi.com, routeway.ai, g4f.dev) and fix Chutes loader
- Add custom loaders for bytez, llm7, aimlapi, routeway, and g4f providers
- Add provider definitions to models-api.json with sample models
- Add provider icon names to types.ts
- Chutes loader already exists and should work with CHUTES_API_KEY env var

Providers added:
- bytez: Uses BYTEZ_API_KEY, OpenAI-compatible
- llm7: Uses LLM7_API_KEY (optional), OpenAI-compatible
- aimlapi: Uses AIMLAPI_API_KEY, OpenAI-compatible
- routeway: Uses ROUTEWAY_API_KEY, OpenAI-compatible
- g4f: Uses G4F_API_KEY (optional), free tier available
2026-02-08 16:07:02 +00:00

83 KiB

Security Remediation Plan

Shopify AI Repository - Comprehensive Security Enhancement

Document Version: 1.0
Created: February 8, 2026
Classification: Internal - Confidential
Risk Level: High Priority Implementation


Executive Summary

This document provides a comprehensive, prioritized plan to address all security vulnerabilities and weaknesses identified in the Shopify AI repository. The plan is structured into phases, with each phase addressing specific security domains and containing actionable remediation steps with technical specifications.

Repository Components in Scope

  1. Chat Application - Node.js monolithic server (17,144 lines)
  2. OpenCode IDE - TypeScript/Bun monorepo with SolidJS/Hono
  3. Windows Desktop App - Tauri-based (Rust + TypeScript)
  4. Container Infrastructure - Docker deployment configuration

Overall Security Posture Assessment

  • Current State: Medium Security Maturity
  • Critical Vulnerabilities: 3 identified
  • High Severity Issues: 12 identified
  • Medium Severity Issues: 18 identified
  • Low Severity Issues: 15 identified

Phase 1: Critical Infrastructure Security

1.1 Replace Custom HTTP Server with Express.js Framework

Priority: Critical
Estimated Effort: 40 hours
Risk Level: Medium (requires thorough testing)

Problem Analysis

The current monolithic server.js (17,144 lines) uses Node.js native http module without framework protections. This eliminates:

  • Built-in security middleware
  • Standardized request validation
  • Framework security updates
  • Community-audited security patterns

Remediation Steps

Step 1.1.1: Install Express.js and Security Middleware
# Navigate to chat directory
cd chat/

# Install Express.js framework
npm install express@^4.19.2

# Install security middleware packages
npm install express-rate-limit@^7.1.5
npm install helmet@^7.1.0
npm install cors@^2.8.5
npm install express-validator@^7.0.1
npm install express-session@^1.18.0
npm install csurf@^1.11.0
npm install hpp@^0.2.3

# Install additional security utilities
npm install xss-clean@^0.1.4
npm install express-mongo-sanitize@^2.2.0
npm install user-agent@^2.1.13
Step 1.1.2: Create Express Server Structure

Create new directory structure:

chat/
├── src/
│   ├── app.js              # Express application setup
│   ├── server.js           # HTTP server entry point
│   ├── routes/             # Route handlers
│   │   ├── auth.js         # Authentication routes
│   │   ├── api.js          # General API routes
│   │   ├── admin.js        # Admin routes
│   │   └── webhook.js      # Webhook handlers
│   ├── middleware/
│   │   ├── auth.js         # Authentication middleware
│   │   ├── rateLimiter.js  # Rate limiting
│   │   ├── security.js     # Security headers
│   │   ├── validation.js  # Request validation
│   │   └── errorHandler.js # Centralized error handling
│   ├── config/
│   │   ├── express.js      # Express configuration
│   │   └── security.js      # Security settings
│   └── utils/
│       ├── logger.js       # Logging utility
│       └── sanitizer.js    # Input sanitization
Step 1.1.3: Implement Express Application (app.js)
// src/app.js
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
const session = require('express-session');
const csurf = require('csurf');

const authRoutes = require('./routes/auth');
const apiRoutes = require('./routes/api');
const adminRoutes = require('./routes/admin');
const webhookRoutes = require('./routes/webhook');
const errorHandler = require('./middleware/errorHandler');
const { requestLogger } = require('./utils/logger');

const app = express();

// Security headers
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.openrouter.ai", "https://api.mistral.ai"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  crossOriginEmbedderPolicy: false,
  crossOriginResourcePolicy: { policy: "cross-origin" }
}));

// CORS configuration
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:4500'],
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
  exposedHeaders: ['X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-RateLimit-Reset']
}));

// Rate limiting - General API
const apiRateLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  message: {
    error: 'Too many requests, please try again later.',
    retryAfter: 60
  },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => {
    return req.ip || req.headers['x-forwarded-for'] || 'unknown';
  },
  skip: (req) => {
    // Skip rate limiting for health checks
    return req.path === '/health' || req.path === '/api/health';
  }
});

// Strict rate limiting for authentication endpoints
const authRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts per window
  message: {
    error: 'Too many authentication attempts. Please try again in 15 minutes.',
    retryAfter: 900
  },
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => {
    // Use fingerprint for better rate limiting
    return `${req.ip}:${req.headers['user-agent'] || 'unknown'}`;
  },
  skipFailedRequests: false
});

// Apply rate limiters
app.use('/api/', apiRateLimiter);
app.use('/auth/', authRateLimiter);

// Body parsing with size limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// Security middleware
app.use(mongoSanitize());
app.use(xss());
app.use(hpp());

// Session management
app.use(session({
  secret: process.env.SESSION_SECRET || process.env.USER_SESSION_SECRET,
  name: 'sessionId',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 60 * 60 * 1000 // 1 hour
  }
}));

// CSRF protection (after session)
const csrfProtection = csurf({
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict'
  },
  ignoreMethods: ['GET', 'HEAD', 'OPTIONS']
});

// Logging
app.use(requestLogger);

// Routes
app.use('/auth', authRoutes);
app.use('/api', apiRoutes);
app.use('/admin', adminRoutes);
app.use('/webhook', webhookRoutes);

// Health check endpoint (no auth required)
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() });
});

// Error handling
app.use(errorHandler);

// 404 handler
app.use((req, res) => {
  res.status(404).json({ error: 'Endpoint not found' });
});

module.exports = app;
Step 1.1.4: Create HTTP Server Entry Point (server.js)
// src/server.js
const app = require('./app');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { logger } = require('./utils/logger');

const PORT = process.env.PORT || 4500;
const HOST = process.env.HOST || '0.0.0.0';

// SSL/TLS configuration for production
let server;
if (process.env.NODE_ENV === 'production') {
  const sslOptions = {
    key: fs.readFileSync(process.env.SSL_KEY_PATH || '/etc/ssl/private/server.key'),
    cert: fs.readFileSync(process.env.SSL_CERT_PATH || '/etc/ssl/certs/server.crt'),
    minVersion: 'TLSv1.2',
    ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384',
    honorCipherOrder: true
  };
  
  server = https.createServer(sslOptions, app);
  logger.info('Starting HTTPS server');
} else {
  const http = require('http');
  server = http.createServer(app);
  logger.info('Starting HTTP server (development mode)');
}

// Graceful shutdown
const gracefulShutdown = (signal) => {
  logger.info(`${signal} received. Starting graceful shutdown...`);
  
  server.close((err) => {
    if (err) {
      logger.error('Error during shutdown:', err);
      process.exit(1);
    }
    
    logger.info('HTTP server closed');
    process.exit(0);
  });
  
  // Force shutdown after 30 seconds
  setTimeout(() => {
    logger.error('Forced shutdown after timeout');
    process.exit(1);
  }, 30000);
};

process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

// Start server
server.listen(PORT, HOST, () => {
  logger.info(`Server running on ${HOST}:${PORT}`);
  logger.info(`Environment: ${process.env.NODE_ENV || 'development'}`);
});

// Unhandled rejection handler
process.on('unhandledRejection', (reason, promise) => {
  logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});

// Uncaught exception handler
process.on('uncaughtException', (error) => {
  logger.error('Uncaught Exception:', error);
  gracefulShutdown('UNCAUGHT_EXCEPTION');
});

module.exports = server;
Step 1.1.5: Migrate Existing Routes

Migrate all existing route handlers from the monolithic server.js to the new route structure. Each route file should:

  • Use middleware for authentication
  • Implement input validation
  • Return standardized response format
  • Handle errors consistently

Verification Steps

  1. Run existing test suite (if available)
  2. Test all authentication flows
  3. Verify rate limiting works
  4. Check security headers with curl/headers tool
  5. Perform penetration testing on key endpoints

1.2 Implement Database with Encryption at Rest

Priority: Critical
Estimated Effort: 60 hours
Risk Level: High (data migration required)

Problem Analysis

Current implementation uses in-memory JSON file storage in chat/.data/ directory:

  • users.json - User accounts, hashed passwords, sessions
  • affiliates.json - Affiliate accounts
  • withdrawals.json - Withdrawal requests
  • feature-requests.json - Feature request tracking
  • contact-messages.json - Contact form messages

Vulnerabilities:

  • No encryption at rest
  • JSON files easily readable if accessed
  • No backup encryption
  • Tampering risk
  • No audit trail

Remediation Steps

Step 1.2.1: Select and Configure Database
cd chat/

# Install PostgreSQL client (or use SQLite for development)
npm install pg@^8.11.3
npm install sqlite3@^5.1.7
npm install better-sqlite3@^9.4.3

# For production: PostgreSQL with encryption
npm install pg-crypto@^1.1.0

# Database migration tool
npm install migrate@^9.2.1
Step 1.2.2: Create Database Schema with Encryption
// src/config/database.js
const { Database } = require('sqlite3');
const crypto = require('crypto');

const dbPath = process.env.DATABASE_PATH || './.data/shopify_ai.db';

// Database encryption key (should be stored in HSM or key management service)
const ENCRYPTION_KEY = process.env.DATABASE_ENCRYPTION_KEY;
const IV_LENGTH = 16;
const ALGORITHM = 'aes-256-gcm';

function encrypt(text) {
  if (!ENCRYPTION_KEY) {
    throw new Error('Database encryption key not configured');
  }
  
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
  
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag();
  
  return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
}

function decrypt(text) {
  if (!ENCRYPTION_KEY) {
    throw new Error('Database encryption key not configured');
  }
  
  const parts = text.split(':');
  const iv = Buffer.from(parts[0], 'hex');
  const authTag = Buffer.from(parts[1], 'hex');
  const encrypted = parts[2];
  
  const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(ENCRYPTION_KEY, 'hex'), iv);
  decipher.setAuthTag(authTag);
  
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

// Initialize database
const db = new Database(dbPath, (err) => {
  if (err) {
    console.error('Database connection error:', err.message);
  } else {
    console.log('Connected to SQLite database');
  }
});

// Enable WAL mode for better performance
db.pragma('journal_mode = WAL');

// Create tables with encrypted fields
db.serialize(() => {
  // Users table
  db.run(`
    CREATE TABLE IF NOT EXISTS users (
      id TEXT PRIMARY KEY,
      email TEXT UNIQUE NOT NULL,
      password_hash TEXT NOT NULL,
      salt TEXT NOT NULL,
      name TEXT,
      role TEXT DEFAULT 'user',
      is_active INTEGER DEFAULT 1,
      created_at INTEGER DEFAULT (unixepoch()),
      updated_at INTEGER DEFAULT (unixepoch()),
      last_login INTEGER,
      failed_login_attempts INTEGER DEFAULT 0,
      locked_until INTEGER,
      two_factor_enabled INTEGER DEFAULT 0,
      two_factor_secret TEXT,
      password_changed_at INTEGER DEFAULT (unixepoch())
    )
  `);
  
  // Sessions table
  db.run(`
    CREATE TABLE IF NOT EXISTS sessions (
      id TEXT PRIMARY KEY,
      user_id TEXT NOT NULL,
      token TEXT NOT NULL,
      expires_at INTEGER NOT NULL,
      ip_address TEXT,
      user_agent TEXT,
      created_at INTEGER DEFAULT (unixepoch()),
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
  
  // Refresh tokens table
  db.run(`
    CREATE TABLE IF NOT EXISTS refresh_tokens (
      id TEXT PRIMARY KEY,
      user_id TEXT NOT NULL,
      token TEXT NOT NULL,
      expires_at INTEGER NOT NULL,
      created_at INTEGER DEFAULT (unixepoch()),
      revoked_at INTEGER,
      revoked_reason TEXT,
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
  
  // Password reset tokens table
  db.run(`
    CREATE TABLE IF NOT EXISTS password_reset_tokens (
      id TEXT PRIMARY KEY,
      user_id TEXT NOT NULL,
      token TEXT NOT NULL,
      expires_at INTEGER NOT NULL,
      used INTEGER DEFAULT 0,
      created_at INTEGER DEFAULT (unixepoch()),
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
  
  // Affiliates table
  db.run(`
    CREATE TABLE IF NOT EXISTS affiliates (
      id TEXT PRIMARY KEY,
      user_id TEXT UNIQUE NOT NULL,
      referral_code TEXT UNIQUE NOT NULL,
      commission_rate REAL DEFAULT 0.10,
      balance REAL DEFAULT 0,
      total_earned REAL DEFAULT 0,
      payout_method TEXT,
      payout_details TEXT,
      is_active INTEGER DEFAULT 1,
      created_at INTEGER DEFAULT (unixepoch()),
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
  
  // Withdrawals table
  db.run(`
    CREATE TABLE IF NOT EXISTS withdrawals (
      id TEXT PRIMARY KEY,
      affiliate_id TEXT NOT NULL,
      amount REAL NOT NULL,
      status TEXT DEFAULT 'pending',
      payment_method TEXT,
      transaction_id TEXT,
      notes TEXT,
      created_at INTEGER DEFAULT (unixepoch()),
      processed_at INTEGER,
      FOREIGN KEY (affiliate_id) REFERENCES affiliates(id)
    )
  `);
  
  // Feature requests table
  db.run(`
    CREATE TABLE IF NOT EXISTS feature_requests (
      id TEXT PRIMARY KEY,
      user_id TEXT NOT NULL,
      title TEXT NOT NULL,
      description TEXT NOT NULL,
      status TEXT DEFAULT 'pending',
      votes INTEGER DEFAULT 0,
      created_at INTEGER DEFAULT (unixepoch()),
      updated_at INTEGER DEFAULT (unixepoch()),
      FOREIGN KEY (user_id) REFERENCES users(id)
    )
  `);
  
  // Contact messages table
  db.run(`
    CREATE TABLE IF NOT EXISTS contact_messages (
      id TEXT PRIMARY KEY,
      user_id TEXT,
      name TEXT NOT NULL,
      email TEXT NOT NULL,
      subject TEXT,
      message TEXT NOT NULL,
      status TEXT DEFAULT 'unread',
      created_at INTEGER DEFAULT (unixepoch()),
      responded_at INTEGER,
      responded_by TEXT
    )
  `);
  
  // Audit log table
  db.run(`
    CREATE TABLE IF NOT EXISTS audit_log (
      id TEXT PRIMARY KEY,
      user_id TEXT,
      action TEXT NOT NULL,
      entity_type TEXT,
      entity_id TEXT,
      old_values TEXT,
      new_values TEXT,
      ip_address TEXT,
      user_agent TEXT,
      created_at INTEGER DEFAULT (unixepoch())
    )
  `);
  
  // Create indexes
  db.run('CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id)');
  db.run('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token ON refresh_tokens(token)');
  db.run('CREATE INDEX IF NOT EXISTS idx_audit_log_user ON audit_log(user_id)');
  db.run('CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at)');
});

// Database helper functions
const dbHelpers = {
  // Run query with parameters
  run(sql, params = []) {
    return new Promise((resolve, reject) => {
      db.run(sql, params, function(err) {
        if (err) reject(err);
        else resolve({ id: this.lastID, changes: this.changes });
      });
    });
  },
  
  // Get single row
  get(sql, params = []) {
    return new Promise((resolve, reject) => {
      db.get(sql, params, (err, row) => {
        if (err) reject(err);
        else resolve(row);
      });
    });
  },
  
  // Get all rows
  all(sql, params = []) {
    return new Promise((resolve, reject) => {
      db.all(sql, params, (err, rows) => {
        if (err) reject(err);
        else resolve(rows);
      });
    });
  },
  
  // Insert with auto-generated ID
  insert(table, data) {
    const columns = Object.keys(data);
    const values = Object.values(data);
    const placeholders = columns.map(() => '?').join(', ');
    
    const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
    return this.run(sql, values);
  },
  
  // Update with encrypted fields
  update(table, data, where, whereParams = []) {
    const updates = Object.keys(data).map(key => `${key} = ?`).join(', ');
    const values = [...Object.values(data), ...whereParams];
    
    const sql = `UPDATE ${table} SET ${updates} WHERE ${where}`;
    return this.run(sql, values);
  }
};

module.exports = { db, dbHelpers, encrypt, decrypt };
Step 1.2.3: Data Migration Script
// scripts/migrate-data.js
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { db, encrypt } = require('../src/config/database');

const DATA_DIR = path.join(__dirname, '../.data');
const BACKUP_DIR = path.join(__dirname, '../.data_backup_' + Date.now());

async function migrate() {
  console.log('Starting data migration...');
  
  // Create backup directory
  fs.mkdirSync(BACKUP_DIR);
  
  // Migrate each data file
  const dataFiles = [
    'users.json',
    'affiliates.json',
    'withdrawals.json',
    'feature-requests.json',
    'contact-messages.json'
  ];
  
  for (const file of dataFiles) {
    const filePath = path.join(DATA_DIR, file);
    
    if (fs.existsSync(filePath)) {
      console.log(`Migrating ${file}...`);
      
      // Backup original
      fs.copyFileSync(filePath, path.join(BACKUP_DIR, file));
      
      // Read and parse
      const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
      
      // Migrate to database
      await migrateFile(file.replace('.json', ''), data);
      
      console.log(`✓ ${file} migrated successfully`);
    }
  }
  
  console.log('Migration complete. Backup created in:', BACKUP_DIR);
}

async function migrateFile(tableName, dataArray) {
  for (const item of dataArray) {
    const id = item.id || crypto.randomUUID();
    const columns = ['id'];
    const placeholders = ['?'];
    const values = [id];
    
    for (const [key, value] of Object.entries(item)) {
      if (key !== 'id') {
        columns.push(key);
        placeholders.push('?');
        // Encrypt sensitive fields
        if (['password', 'password_hash', 'salt', 'two_factor_secret', 'payout_details'].includes(key)) {
          values.push(encrypt(String(value)));
        } else {
          values.push(typeof value === 'object' ? JSON.stringify(value) : value);
        }
      }
    }
    
    const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
    await db.run(sql, values);
  }
}

// Run migration
migrate().catch(console.error);
Step 1.2.4: Implement Audit Trail
// src/middleware/auditLogger.js
const { db, dbHelpers } = require('../config/database');
const crypto = require('crypto');

async function auditLog(req, action, entityType, entityId, oldValues = null, newValues = null) {
  const auditEntry = {
    id: crypto.randomUUID(),
    user_id: req.user?.id || null,
    action,
    entity_type: entityType,
    entity_id: entityId,
    old_values: oldValues ? JSON.stringify(oldValues) : null,
    new_values: newValues ? JSON.stringify(newValues) : null,
    ip_address: req.ip || req.headers['x-forwarded-for'],
    user_agent: req.headers['user-agent']
  };
  
  await dbHelpers.insert('audit_log', auditEntry);
}

// Middleware for automatic audit logging
function auditMiddleware(action, entityType) {
  return async (req, res, next) => {
    const originalSend = res.send;
    let responseBody;
    
    res.send = function(body) {
      responseBody = body;
      return originalSend.apply(this, arguments);
    };
    
    res.on('finish', async () => {
      if (res.statusCode >= 200 && res.statusCode < 300) {
        try {
          await auditLog(req, action, entityType, req.params.id, null, JSON.parse(responseBody));
        } catch (e) {
          console.error('Audit log error:', e);
        }
      }
    });
    
    next();
  };
}

module.exports = { auditLog, auditMiddleware };

Verification Steps

  1. Verify all data migrated correctly
  2. Test encryption/decryption operations
  3. Perform database integrity checks
  4. Test backup and restore procedures
  5. Verify audit logging captures all actions

1.3 Implement Session Revocation and Token Management

Priority: Critical
Estimated Effort: 20 hours
Risk Level: Medium

Problem Analysis

Current implementation lacks:

  • Session revocation mechanism
  • Token blacklisting
  • Session enumeration protection
  • Device fingerprinting

Remediation Steps

Step 1.3.1: Implement JWT Token Manager
// src/utils/tokenManager.js
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const { db, dbHelpers } = require('../config/database');

const JWT_SECRET = process.env.JWT_SECRET || process.env.USER_SESSION_SECRET;
const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';
const RESET_TOKEN_EXPIRY = '1h';

class TokenManager {
  // Generate access token
  generateAccessToken(user) {
    return jwt.sign(
      {
        id: user.id,
        email: user.email,
        role: user.role,
        type: 'access'
      },
      JWT_SECRET,
      { expiresIn: ACCESS_TOKEN_EXPIRY }
    );
  }
  
  // Generate refresh token
  async generateRefreshToken(user, req) {
    const token = crypto.randomBytes(64).toString('hex');
    const expiresAt = Date.now() + (7 * 24 * 60 * 60 * 1000); // 7 days
    
    await dbHelpers.insert('refresh_tokens', {
      id: crypto.randomUUID(),
      user_id: user.id,
      token: await this.hashToken(token),
      expires_at: expiresAt,
      ip_address: req.ip || req.headers['x-forwarded-for'],
      user_agent: req.headers['user-agent']
    });
    
    return { token, expiresAt };
  }
  
  // Hash token for storage
  async hashToken(token) {
    const salt = await crypto.randomBytes(16).toString('hex');
    const hash = await crypto.pbkdf2Async(token, salt, 100000, 64, 'sha512');
    return salt + ':' + hash.toString('hex');
  }
  
  // Verify refresh token
  async verifyRefreshToken(token, userId) {
    const hashedToken = await this.hashToken(token);
    const storedToken = await dbHelpers.get(
      'SELECT * FROM refresh_tokens WHERE user_id = ? AND token = ? AND used = 0 AND expires_at > ?',
      [userId, hashedToken, Date.now()]
    );
    
    return storedToken || null;
  }
  
  // Revoke refresh token
  async revokeRefreshToken(token, userId, reason = 'user_logout') {
    const hashedToken = await this.hashToken(token);
    await dbHelpers.update(
      'refresh_tokens',
      { used: 1, revoked_at: Date.now(), revoked_reason: reason },
      'user_id = ? AND token = ?',
      [userId, hashedToken]
    );
  }
  
  // Revoke all user tokens
  async revokeAllUserTokens(userId, reason = 'security_reset') {
    await dbHelpers.run(
      'UPDATE refresh_tokens SET used = 1, revoked_at = ?, revoked_reason = ? WHERE user_id = ? AND used = 0',
      [Date.now(), reason, userId]
    );
  }
  
  // Generate password reset token
  async generatePasswordResetToken(user) {
    const token = crypto.randomBytes(32).toString('hex');
    const expiresAt = Date.now() + (60 * 60 * 1000); // 1 hour
    
    // Invalidate old tokens
    await dbHelpers.update(
      'password_reset_tokens',
      { used: 1 },
      'user_id = ? AND used = 0',
      [user.id]
    );
    
    await dbHelpers.insert('password_reset_tokens', {
      id: crypto.randomUUID(),
      user_id: user.id,
      token: await this.hashToken(token),
      expires_at: expiresAt
    });
    
    return { token, expiresAt };
  }
  
  // Verify and use password reset token
  async verifyAndUsePasswordResetToken(token, userId) {
    const hashedToken = await this.hashToken(token);
    const resetToken = await dbHelpers.get(
      'SELECT * FROM password_reset_tokens WHERE user_id = ? AND token = ? AND used = 0 AND expires_at > ?',
      [userId, hashedToken, Date.now()]
    );
    
    if (resetToken) {
      await dbHelpers.update(
        'password_reset_tokens',
        { used: 1 },
        'id = ?',
        [resetToken.id]
      );
      return true;
    }
    
    return false;
  }
}

module.exports = new TokenManager();
Step 1.3.2: Implement Session Fingerprinting
// src/middleware/sessionFingerprint.js
const crypto = require('crypto');

function generateFingerprint(req) {
  const components = [
    req.headers['user-agent'] || '',
    req.headers['accept-language'] || '',
    req.headers['accept-encoding'] || '',
    req.ip || '',
    req.headers['x-forwarded-for'] || ''
  ];
  
  return crypto
    .createHash('sha256')
    .update(components.join('|'))
    .digest('hex')
    .substring(0, 32);
}

function validateFingerprint(req, storedFingerprint) {
  const currentFingerprint = generateFingerprint(req);
  return crypto.timingSafeEqual(
    Buffer.from(currentFingerprint),
    Buffer.from(storedFingerprint)
  );
}

module.exports = { generateFingerprint, validateFingerprint };

Verification Steps

  1. Test token generation and verification
  2. Verify token revocation works
  3. Test session fingerprinting
  4. Check token expiry handling

Phase 2: Authentication Security Enhancement

2.1 Strengthen Password Authentication

Priority: High
Estimated Effort: 16 hours
Risk Level: Low

Problem Analysis

Current password security:

  • bcrypt with 12 salt rounds ✓
  • Password policy (12+ chars, complexity) ✓
  • Account lockout (5 attempts/15 min) ✓

Improvements needed:

  • Password strength meter
  • Breach detection (HaveIBeenPwned API)
  • Password history enforcement
  • Progressive delays on failed attempts

Remediation Steps

Step 2.1.1: Enhanced Password Validator
// src/utils/passwordValidator.js
const bcrypt = require('bcrypt');
const crypto = require('crypto');

const SALT_ROUNDS = 12;

class PasswordValidator {
  constructor() {
    this.bannedPasswords = new Set([
      'password', '123456', '12345678', 'qwerty', 'abc123',
      'password123', 'admin123', 'letmein', 'welcome'
    ]);
  }
  
  validate(password, email = '') {
    const errors = [];
    
    // Length check
    if (password.length < 12) {
      errors.push('Password must be at least 12 characters long');
    }
    
    // Complexity requirements
    if (!/[A-Z]/.test(password)) {
      errors.push('Password must contain at least one uppercase letter');
    }
    if (!/[a-z]/.test(password)) {
      errors.push('Password must contain at least one lowercase letter');
    }
    if (!/[0-9]/.test(password)) {
      errors.push('Password must contain at least one number');
    }
    if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
      errors.push('Password must contain at least one special character');
    }
    
    // Common password check
    if (this.bannedPasswords.has(password.toLowerCase())) {
      errors.push('Password is too common');
    }
    
    // Email-based password check
    const emailPart = email.split('@')[0];
    if (emailPart && password.toLowerCase().includes(emailPart.toLowerCase())) {
      errors.push('Password cannot contain your email username');
    }
    
    // Sequential character check
    if (this.hasSequentialChars(password)) {
      errors.push('Password cannot contain more than 3 sequential characters');
    }
    
    return {
      isValid: errors.length === 0,
      errors
    };
  }
  
  hasSequentialChars(password) {
    const lower = password.toLowerCase();
    for (let i = 0; i < lower.length - 2; i++) {
      const c1 = lower.charCodeAt(i);
      const c2 = lower.charCodeAt(i + 1);
      const c3 = lower.charCodeAt(i + 2);
      if (c2 === c1 + 1 && c3 === c1 + 2) {
        return true;
      }
    }
    return false;
  }
  
  async hash(password) {
    return bcrypt.hash(password, SALT_ROUNDS);
  }
  
  async compare(password, hash) {
    return bcrypt.compare(password, hash);
  }
  
  async checkBreachedPassword(password) {
    // Hash password with SHA-1
    const sha1Hash = crypto
      .createHash('sha1')
      .update(password)
      .digest('hex')
      .toUpperCase();
    
    const prefix = sha1Hash.substring(0, 5);
    const suffix = sha1Hash.substring(5);
    
    try {
      const response = await fetch(
        `https://api.pwnedpasswords.com/range/${prefix}`,
        { headers: { 'Add-Padding': 'true' } }
      );
      
      const text = await response.text();
      const lines = text.split('\n');
      
      for (const line of lines) {
        const [hashSuffix, count] = line.split(':');
        if (hashSuffix.trim() === suffix) {
          return { pwned: true, count: parseInt(count, 10) };
        }
      }
      
      return { pwned: false, count: 0 };
    } catch (error) {
      console.error('Breach check failed:', error);
      return { error: true };
    }
  }
}

module.exports = new PasswordValidator();
Step 2.1.2: Progressive Account Lockout
// src/middleware/progressiveLockout.js
const { db, dbHelpers } = require('../config/database');
const crypto = require('crypto');

class ProgressiveLockout {
  getLockoutDuration(failedAttempts) {
    if (failedAttempts >= 10) return 24 * 60 * 60 * 1000; // 24 hours
    if (failedAttempts >= 7) return 12 * 60 * 60 * 1000;  // 12 hours
    if (failedAttempts >= 5) return 60 * 60 * 1000;        // 1 hour
    if (failedAttempts >= 3) return 15 * 60 * 1000;        // 15 minutes
    return 5 * 60 * 1000;                                  // 5 minutes
  }
  
  async checkLockout(email) {
    const user = await dbHelpers.get(
      'SELECT id, email, failed_login_attempts, locked_until FROM users WHERE email = ? AND is_active = 1',
      [email]
    );
    
    if (!user) {
      // Generic error message to prevent enumeration
      return { locked: false, error: 'Invalid credentials' };
    }
    
    if (user.locked_until && user.locked_until > Date.now()) {
      const remainingTime = Math.ceil((user.locked_until - Date.now()) / 60000);
      return {
        locked: true,
        error: `Account locked. Try again in ${remainingTime} minutes.`,
        retryAfter: remainingTime * 60
      };
    }
    
    return { locked: false, user };
  }
  
  async recordFailedAttempt(userId) {
    const user = await dbHelpers.get(
      'SELECT failed_login_attempts, locked_until FROM users WHERE id = ?',
      [userId]
    );
    
    const failedAttempts = (user?.failed_login_attempts || 0) + 1;
    const lockoutDuration = this.getLockoutDuration(failedAttempts);
    
    let lockedUntil = null;
    if (failedAttempts >= 5) {
      lockedUntil = Date.now() + lockoutDuration;
    }
    
    await dbHelpers.update(
      'users',
      {
        failed_login_attempts: failedAttempts,
        locked_until: lockedUntil
      },
      'id = ?',
      [userId]
    );
    
    return { failedAttempts, locked: lockedUntil !== null };
  }
  
  async resetFailedAttempts(userId) {
    await dbHelpers.update(
      'users',
      { failed_login_attempts: 0, locked_until: null },
      'id = ?',
      [userId]
    );
  }
}

module.exports = new ProgressiveLockout();

Verification Steps

  1. Test all password validation rules
  2. Verify breach detection integration
  3. Test progressive lockout timing
  4. Check generic error messages prevent enumeration

2.2 Implement Two-Factor Authentication

Priority: High
Estimated Effort: 24 hours
Risk Level: Medium

Remediation Steps

Step 2.2.1: TOTP Implementation
// src/utils/totp.js
const crypto = require('crypto');
const base32 = require('hi-base32');

const DIGITS = 6;
const PERIOD = 30;
const ALGORITHM = 'sha1';

class TOTP {
  generateSecret(length = 20) {
    return crypto.randomBytes(length).toString('hex');
  }
  
  generateSecretBase32() {
    const buffer = crypto.randomBytes(10);
    return base32.encode(buffer).replace(/=/g, '');
  }
  
  getotpauthURL(secret, issuer, account, label) {
    const encodedIssuer = encodeURIComponent(issuer);
    const encodedAccount = encodeURIComponent(account);
    const encodedSecret = secret.replace(/ /g, '');
    
    return `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${encodedSecret}&issuer=${encodedIssuer}&algorithm=${ALGORITHM}&digits=${DIGITS}&period=${PERIOD}`;
  }
  
  verify(token, secret, window = 1) {
    const epoch = Math.floor(Date.now() / 1000);
    const timeStep = Math.floor(epoch / PERIOD);
    
    for (let i = -window; i <= window; i++) {
      const time = timeStep + i;
      const generatedToken = this.generateToken(secret, time);
      
      if (this.timingSafeEqual(token, generatedToken)) {
        return { valid: true, delta: i };
      }
    }
    
    return { valid: false };
  }
  
  generateToken(secret, time) {
    const buffer = Buffer.alloc(8);
    buffer.writeBigUInt64BE(BigInt(time), 0);
    
    const decodedSecret = base32.decode(secret.replace(/ /g, ''));
    const hmac = crypto.createHmac(ALGORITHM, decodedSecret);
    hmac.update(buffer);
    const hash = hmac.digest();
    
    const offset = hash[hash.length - 1] & 0xf;
    const truncatedHash = hash.readUInt32BE(offset) & 0x7fffffff;
    const token = truncatedHash % Math.pow(10, DIGITS);
    
    return token.toString().padStart(DIGITS, '0');
  }
  
  timingSafeEqual(a, b) {
    if (a.length !== b.length) return false;
    return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
  }
}

module.exports = new TOTP();
Step 2.2.2: Two-Factor Authentication Routes
// src/routes/twoFactor.js
const express = require('express');
const router = express.Router();
const { db, dbHelpers } = require('../config/database');
const crypto = require('crypto');
const totp = require('../utils/totp');
const passwordValidator = require('../utils/passwordValidator');
const { requireUserAuth } = require('../middleware/auth');

// Generate 2FA setup
router.post('/setup', requireUserAuth, async (req, res) => {
  try {
    const user = await dbHelpers.get('SELECT * FROM users WHERE id = ?', [req.user.id]);
    
    // Generate new secret
    const secret = totp.generateSecretBase32();
    
    // Generate recovery codes
    const recoveryCodes = Array(10).fill(0).map(() => 
      crypto.randomBytes(4).toString('hex').toUpperCase()
    );
    
    // Store temporarily (not yet enabled)
    await dbHelpers.update(
      'users',
      {
        two_factor_secret: passwordValidator.hash(secret), // Store encrypted
        two_factor_temp_secret: secret,
        two_factor_recovery_codes: await passwordValidator.hash(JSON.stringify(recoveryCodes))
      },
      'id = ?',
      [req.user.id]
    );
    
    // Generate QR code URL
    const otpauthURL = totp.getotpauthURL(
      secret,
      'Shopify AI',
      user.email,
      `${user.name || user.email} (Shopify AI)`
    );
    
    res.json({
      success: true,
      secret,
      otpauthURL,
      recoveryCodes // Show only once
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to setup 2FA' });
  }
});

// Verify and enable 2FA
router.post('/enable', requireUserAuth, async (req, res) => {
  try {
    const { token } = req.body;
    const user = await dbHelpers.get('SELECT two_factor_temp_secret FROM users WHERE id = ?', [req.user.id]);
    
    if (!user?.two_factor_temp_secret) {
      return res.status(400).json({ error: '2FA setup not initiated' });
    }
    
    const result = totp.verify(token, user.two_factor_temp_secret);
    
    if (!result.valid) {
      return res.status(400).json({ error: 'Invalid verification code' });
    }
    
    // Enable 2FA
    await dbHelpers.update(
      'users',
      {
        two_factor_secret: user.two_factor_temp_secret,
        two_factor_temp_secret: null,
        two_factor_enabled: 1
      },
      'id = ?',
      [req.user.id]
    );
    
    res.json({ success: true, message: '2FA enabled successfully' });
  } catch (error) {
    res.status(500).json({ error: 'Failed to enable 2FA' });
  }
});

// Verify 2FA during login
router.post('/verify', async (req, res) => {
  try {
    const { email, password, token } = req.body;
    
    // First verify password
    const user = await dbHelpers.get(
      'SELECT * FROM users WHERE email = ? AND is_active = 1',
      [email]
    );
    
    if (!user || !(await passwordValidator.compare(password, user.password_hash))) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    if (!user.two_factor_enabled) {
      return res.status(400).json({ error: '2FA not enabled for this account' });
    }
    
    const result = totp.verify(token, user.two_factor_secret);
    
    if (!result.valid) {
      return res.status(401).json({ error: 'Invalid 2FA code' });
    }
    
    // Generate session
    const tokenManager = require('../utils/tokenManager');
    const accessToken = tokenManager.generateAccessToken(user);
    const refreshToken = await tokenManager.generateRefreshToken(user, req);
    
    res.json({
      success: true,
      accessToken,
      refreshToken: refreshToken.token,
      expiresAt: refreshToken.expiresAt
    });
  } catch (error) {
    res.status(500).json({ error: '2FA verification failed' });
  }
});

module.exports = router;

Verification Steps

  1. Test TOTP generation and verification
  2. Verify QR code scanning works
  3. Test recovery codes
  4. Test backup codes

Phase 3: Input Validation & Sanitization Enhancement

3.1 Comprehensive Input Sanitization Framework

Priority: High
Estimated Effort: 20 hours
Risk Level: Medium

Remediation Steps

Step 3.1.1: Create Comprehensive Sanitizer
// src/utils/sanitizer.js
class Sanitizer {
  // HTML sanitization
  sanitizeHTML(input) {
    if (typeof input !== 'string') return input;
    
    return input
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;')
      .replace(/\//g, '&#x2F;');
  }
  
  // XSS prevention for user-generated content
  sanitizeUserContent(input) {
    if (typeof input !== 'string') return input;
    
    return input
      .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
      .replace(/javascript:/gi, '')
      .replace(/on\w+=/gi, '')
      .replace(/<iframe/gi, '')
      .replace(/<object/gi, '')
      .replace(/<embed/gi, '')
      .replace(/<link/gi, '')
      .replace(/<meta/gi, '');
  }
  
  // SQL injection prevention
  sanitizeSQL(input) {
    if (typeof input !== 'string') return input;
    
    return input
      .replace(/'/g, "''")
      .replace(/;/g, '')
      .replace(/--/g, '')
      .replace(/\/\*/g, '')
      .replace(/\*\//g, '')
      .replace(/xp_/gi, '')
      .replace(/EXEC/gi, 'EXEC ');
  }
  
  // File path sanitization
  sanitizePath(input, allowedRoot = '/home/web/data') {
    if (typeof input !== 'string') return null;
    
    // Remove null bytes
    input = input.replace(/\0/g, '');
    
    // Remove traversal attempts
    input = input.replace(/\.\.\//g, '');
    input = input.replace(/\.\.\\/g, '');
    
    // Remove absolute path attempts
    if (input.startsWith('/') || input.match(/^[a-z]:\\/i)) {
      return null;
    }
    
    // Ensure it stays within allowed root
    const fullPath = path.join(allowedRoot, input);
    if (!fullPath.startsWith(path.normalize(allowedRoot))) {
      return null;
    }
    
    return fullPath;
  }
  
  // Email validation and sanitization
  sanitizeEmail(input) {
    if (typeof input !== 'string') return null;
    
    const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
    const sanitized = input.trim().toLowerCase().substring(0, 254);
    
    return emailRegex.test(sanitized) ? sanitized : null;
  }
  
  // Phone number sanitization
  sanitizePhone(input) {
    if (typeof input !== 'string') return null;
    
    const cleaned = input.replace(/[^\d+]/g, '');
    const phoneRegex = /^\+?[1-9]\d{1,14}$/;
    
    return phoneRegex.test(cleaned) ? cleaned : null;
  }
  
  // Username sanitization
  sanitizeUsername(input) {
    if (typeof input !== 'string') return null;
    
    return input
      .trim()
      .substring(0, 50)
      .replace(/[^a-zA-Z0-9_-]/g, '');
  }
  
  // AI prompt sanitization
  sanitizePrompt(input) {
    if (typeof input !== 'string') return '';
    
    const patterns = [
      /ignore\s+previous\s+instructions/gi,
      /system\s*:/gi,
      /you\s+are\s+a/gi,
      /pretend\s+to\s+be/gi,
      /角色扮演/gi,
      /jailbreak/gi,
      /prompt\s+injection/gi,
      /\b(SUDO|ADMIN|ROOT)\b/gi,
      /\{\{.*\}\}/g,
      /\[\[.*\]\]/g,
      /\(\(.*\)\)/g
    ];
    
    let sanitized = input;
    for (const pattern of patterns) {
      sanitized = sanitized.replace(pattern, '[FILTERED]');
    }
    
    return sanitized;
  }
  
  // JSON sanitization
  sanitizeJSON(input) {
    if (typeof input !== 'string') return input;
    
    try {
      const parsed = JSON.parse(input);
      return this.sanitizeObject(parsed);
    } catch {
      return null;
    }
  }
  
  // Deep object sanitization
  sanitizeObject(obj, maxDepth = 10) {
    if (maxDepth <= 0) return null;
    
    if (Array.isArray(obj)) {
      return obj.map(item => this.sanitizeObject(item, maxDepth - 1));
    }
    
    if (obj !== null && typeof obj === 'object') {
      const sanitized = {};
      for (const [key, value] of Object.entries(obj)) {
        const sanitizedKey = this.sanitizeSQL(key);
        if (sanitizedKey) {
          sanitized[sanitizedKey] = this.sanitizeObject(value, maxDepth - 1);
        }
      }
      return sanitized;
    }
    
    return this.sanitizeHTML(String(obj));
  }
}

module.exports = new Sanitizer();
Step 3.1.2: Request Validation Middleware
// src/middleware/validation.js
const { body, param, query, validationResult } = require('express-validator');
const sanitizer = require('../utils/sanitizer');

const validators = {
  // User registration validation
  register: [
    body('email')
      .trim()
      .isEmail()
      .normalizeEmail()
      .withMessage('Valid email is required'),
    body('password')
      .isLength({ min: 12 })
      .withMessage('Password must be at least 12 characters')
      .matches(/[A-Z]/)
      .withMessage('Password must contain uppercase')
      .matches(/[a-z]/)
      .withMessage('Password must contain lowercase')
      .matches(/[0-9]/)
      .withMessage('Password must contain number')
      .matches(/[!@#$%^&*(),.?":{}|<>]/)
      .withMessage('Password must contain special character'),
    body('name')
      .trim()
      .isLength({ min: 2, max: 100 })
      .withMessage('Name must be 2-100 characters')
      .matches(/^[a-zA-Z\s'-]+$/)
      .withMessage('Name contains invalid characters')
  ],
  
  // Login validation
  login: [
    body('email')
      .trim()
      .isEmail()
      .normalizeEmail()
      .withMessage('Valid email is required'),
    body('password')
      .notEmpty()
      .withMessage('Password is required')
  ],
  
  // Chat message validation
  chatMessage: [
    body('message')
      .isString()
      .isLength({ min: 1, max: 10000 })
      .withMessage('Message must be 1-10000 characters')
      .trim()
  ],
  
  // App upload validation
  appUpload: [
    body('appName')
      .trim()
      .isLength({ min: 3, max: 100 })
      .withMessage('App name must be 3-100 characters')
      .matches(/^[a-zA-Z0-9_-]+$/)
      .withMessage('App name can only contain alphanumeric characters, hyphens, and underscores'),
    body('description')
      .optional()
      .trim()
      .isLength({ max: 500 })
      .withMessage('Description must be less than 500 characters')
  ],
  
  // Pagination validation
  pagination: [
    query('page')
      .optional()
      .isInt({ min: 1 })
      .withMessage('Page must be a positive integer'),
    query('limit')
      .optional()
      .isInt({ min: 1, max: 100 })
      .withMessage('Limit must be 1-100')
  ],
  
  // ID parameter validation
  idParam: [
    param('id')
      .isUUID()
      .withMessage('Invalid ID format')
  ]
};

// Validation result handler
function validate(req, res, next) {
  const errors = validationResult(req);
  
  if (!errors.isEmpty()) {
    return res.status(400).json({
      error: 'Validation failed',
      details: errors.array().map(e => ({
        field: e.path,
        message: e.msg
      }))
    });
  }
  
  // Sanitize validated input
  req.sanitizedBody = {};
  for (const [key, value] of Object.entries(req.body)) {
    req.sanitizedBody[key] = sanitizer.sanitizeUserContent(value);
  }
  
  next();
}

module.exports = { validators, validate };

Verification Steps

  1. Test all sanitization functions
  2. Verify XSS prevention
  3. Test SQL injection prevention
  4. Validate file path sanitization

3.2 Enhanced AI Prompt Injection Protection

Priority: High
Estimated Effort: 16 hours
Risk Level: Medium

Remediation Steps

Step 3.2.1: Multi-Layer Prompt Sanitizer
// security/prompt-sanitizer-enhanced.js
class PromptSanitizer {
  constructor() {
    // Layer 1: Known injection patterns
    this.injectionPatterns = [
      // Direct instructions
      /\bignore\s+(?:all\s+)?(?:previous\s+)?instructions?\b/gi,
      /\bignore\s+(?:the\s+)?(?:above\s+)?(?:guidelines?|rules?|context)\b/gi,
      /\bdisregard\s+(?:all\s+)?(?:previous\s+)?(?:instructions?|guidelines?)\b/gi,
      /\bdo\s+not\s+(?:follow\s+)?(?:any\s+)?(?:previous\s+)?(?:instructions?|guidelines?)\b/gi,
      
      // Roleplaying attempts
      /\b(you\s+are\s+a|act\s+as\s+a|pretend\s+to\s+be|roleplay\s+as)\b/gi,
      /\bcharacter\s+is\b.*\b(evil|malicious|hacker)\b/gi,
      /\bdan\s+(?:mode|gpt\s+runner)\b/gi,
      /\bdev\s+mode\b/gi,
      
      // System prompt extraction
      /\{(?:system|prompt|context|instructions)[\s:]*\}/gi,
      /\[\[(?:system|prompt|context|instructions)[\s\]]*\]/gi,
      /\(\((?:system|prompt|context|instructions)[\s\)]\)\)/gi,
      /<\|system(?:[\s]|)>/gi,
      
      // Encoding attempts
      /base64[:\s]*[A-Za-z0-9+/=]+/gi,
      /url\s*encoding[:\s]*[A-Za-z0-9%-]+/gi,
      /\b(?:eval|exec|execSync|spawn)\s*\(/gi,
      
      // Shell commands
      /(?:;|\||`|\$)\s*(?:sh|bash|cmd|powershell)/gi,
      /&&.*(?:rm|cat|ls|wget|curl)/gi,
      
      // SQL injection patterns
      /['";].*?(?:DROP|DELETE|INSERT|UPDATE|SELECT)\b/gi,
      /UNION\s+(?:ALL\s+)?SELECT/gi,
      
      // Markdown injection
      /\[(?:system|prompt|hidden)\]/gi,
      /```(?:system|prompt|hidden)/gi,
      
      // Multilingual bypass attempts
      /角色扮演/gi,
      /ignore前面的指令/gi,
      /ignorer\s+les\s+instructions/gi,
      /ignorar\s+las\s+instrucciones/gi,
      
      // Template injection
      /\{\{.*\}\}/g,
      /\[\[.*\]\]/g,
      /\(\(.*\)\)/g,
      /#\{.*\}/g,
      
      // AI-specific jailbreaks
      /\bdaniel.*\b(升高|越高)/gi,
      /\b草莓蛋糕\b/gi,
      /\bchevalier.*?(?:démon|demon)\b/gi,
      
      // Privilege escalation
      /\bsudo\b/gi,
      /\broot\b.*?\b(access|permission)\b/gi,
      /\badmin\b.*?\b(?:mode|privilege)\b/gi
    ];
    
    // Layer 2: Context manipulation patterns
    this.contextPatterns = [
      /new\s+system\s+message/gi,
      /overwrite\s+system/gi,
      /change\s+(?:your\s+)?(?:behavior|personality)/gi,
      /system\s+override/gi,
      /override\s+(?:security\s+)?(?:restrictions?|rules?)/gi
    ];
    
    // Layer 3: Cognitive exploitation patterns
    this.cognitivePatterns = [
      /(?:white hat|ethical hacking|security research)/gi,
      /(?:test|demo|example)\s+(?:purpose|scenario)/gi,
      /(?:forgot|remember)\s+(?:the\s+)?(?:rules?|context)/gi
    ];
  }
  
  // Primary sanitization method
  sanitize(input) {
    if (typeof input !== 'string') {
      return { clean: '', blocked: true, reason: 'Invalid input type' };
    }
    
    let sanitized = input;
    const blockedPatterns = [];
    
    // Check and remove injection patterns
    for (const pattern of this.injectionPatterns) {
      if (pattern.test(sanitized)) {
        blockedPatterns.push(pattern.source.substring(0, 50));
        sanitized = sanitized.replace(pattern, '[INJECTION_BLOCKED]');
      }
    }
    
    // Check context manipulation
    for (const pattern of this.contextPatterns) {
      if (pattern.test(sanitized)) {
        blockedPatterns.push(pattern.source.substring(0, 50));
        sanitized = sanitized.replace(pattern, '[CONTEXT_MANIPULATION_BLOCKED]');
      }
    }
    
    // Check cognitive exploitation
    for (const pattern of this.cognitivePatterns) {
      if (pattern.test(sanitized)) {
        blockedPatterns.push(pattern.source.substring(0, 50));
        sanitized = sanitized.replace(pattern, '[COGNITIVE_EXPLOITATION_BLOCKED]');
      }
    }
    
    // Length validation
    const maxLength = 8000;
    if (sanitized.length > maxLength) {
      sanitized = sanitized.substring(0, maxLength);
      blockedPatterns.push('Input exceeded maximum length');
    }
    
    // Check for high entropy (potential encoded content)
    if (this.detectHighEntropy(sanitized)) {
      sanitized = '[HIGH_ENTROPY_CONTENT_REVIEWED]' + sanitized;
    }
    
    return {
      clean: sanitized.trim(),
      blocked: blockedPatterns.length > 0,
      detectedPatterns: blockedPatterns,
      timestamp: new Date().toISOString()
    };
  }
  
  // Detect potential encoded/masked content
  detectHighEntropy(input) {
    const entropy = this.calculateEntropy(input);
    return entropy > 4.5 && input.length > 100;
  }
  
  calculateEntropy(input) {
    const frequencies = {};
    for (const char of input) {
      frequencies[char] = (frequencies[char] || 0) + 1;
    }
    
    let entropy = 0;
    const len = input.length;
    
    for (const char in frequencies) {
      const p = frequencies[char] / len;
      entropy -= p * Math.log2(p);
    }
    
    return entropy;
  }
  
  // Additional context isolation
  isolateContext(userInput, systemPrompt) {
    return {
      system: systemPrompt,
      user: userInput,
      wrapped: `--- SYSTEM CONTEXT ---\n${systemPrompt}\n--- END SYSTEM CONTEXT ---\n\n--- USER INPUT ---\n${userInput}\n--- END USER INPUT ---`
    };
  }
  
  // Audit logging for detected attacks
  logAttempt(input, result, metadata = {}) {
    return {
      timestamp: new Date().toISOString(),
      inputLength: input.length,
      sanitizedLength: result.clean.length,
      blocked: result.blocked,
      patterns: result.detectedPatterns,
      ...metadata
    };
  }
}

module.exports = new PromptSanitizer();
Step 3.2.2: AI Request Router with Protection
// src/middleware/aiProtection.js
const promptSanitizer = require('../security/prompt-sanitizer-enhanced');
const { auditLog } = require('../middleware/auditLogger');

async function aiRequestProtection(req, res, next) {
  const userInput = req.body.message || req.body.prompt || req.body.input;
  
  if (!userInput) {
    return next();
  }
  
  const result = promptSanitizer.sanitize(userInput);
  
  // Log potential attacks
  if (result.blocked) {
    await auditLog(
      req,
      'PROMPT_INJECTION_ATTEMPT',
      'ai_request',
      req.user?.id || 'anonymous',
      null,
      { detectedPatterns: result.detectedPatterns }
    );
    
    console.warn('Prompt injection detected:', {
      user: req.user?.id || 'anonymous',
      patterns: result.detectedPatterns,
      timestamp: result.timestamp
    });
    
    // In development, allow with warning
    if (process.env.NODE_ENV === 'development') {
      console.warn('Dev mode: Allowing blocked input');
      req.body.sanitizedMessage = result.clean;
      return next();
    }
    
    return res.status(400).json({
      error: 'Request blocked',
      message: 'Your input contained potentially harmful patterns and was blocked.',
      detectedPatterns: result.detectedPatterns
    });
  }
  
  // Store sanitized input
  req.body.sanitizedMessage = result.clean;
  
  next();
}

module.exports = { aiRequestProtection };

Verification Steps

  1. Test against known jailbreak prompts
  2. Test encoding bypass attempts
  3. Verify logging captures all attempts
  4. Test with various languages

Phase 4: File Upload Security

4.1 Secure File Upload Implementation

Priority: High
Estimated Effort: 16 hours
Risk Level: High

Remediation Steps

Step 4.1.1: Secure File Upload Handler
// src/utils/fileUploader.js
const crypto = require('crypto');
const path = require('path');
const fs = require('fs').promises;
const { promisify } = require('util');
const stream = require('stream');
const pipeline = promisify(stream.pipeline);

const UPLOAD_DIR = process.env.UPLOAD_DIR || '/home/web/data/uploads';
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
const ALLOWED_MIME_TYPES = {
  'image/jpeg': ['.jpg', '.jpeg'],
  'image/png': ['.png'],
  'image/gif': ['.gif'],
  'image/webp': ['.webp'],
  'application/pdf': ['.pdf'],
  'text/plain': ['.txt'],
  'text/markdown': ['.md'],
  'application/zip': ['.zip'],
  'application/x-zip-compressed': ['.zip']
};

const DANGEROUS_EXTENSIONS = [
  '.exe', '.bat', '.cmd', '.com', '.pif', '.scr',
  '.js', '.jse', '.mjs', '.cjs',
  '.vbs', '.vbe', '.wsh', '.wsc', '.wsf', '.wst',
  '.ps1', '.ps2', '.ps1xml', '.ps2xml', '.psc1', '.psc2',
  '.php', '.phtml', '.phar',
  '.asp', '.aspx', '.cer', '.cfm', '.cgi', '.pl', '.py', '.rb',
  '.jar', '.war', '.ear',
  '.sh', '.bash', '.zsh', '.fish',
  '.msi', '.appx', '.appxbundle',
  '.dll', '.sys', '.drv', '.ocx',
  '.html', '.htm', '.xhtml', '.shtml',
  '.svg', '.xml', '.xsl', '.xslt'
];

const MAGIC_BYTES = {
  'image/jpeg': ['FF D8 FF'],
  'image/png': ['89 50 4E 47 0D 0A 1A 0A'],
  'image/gif': ['47 49 46 38'],
  'image/webp': ['52 49 46 46'],
  'application/pdf': ['25 50 44 46'],
  'application/zip': ['50 4B 03 04', '50 4B 05 06', '50 4B 07 08']
};

class SecureFileUploader {
  constructor() {
    this.ensureUploadDir();
  }
  
  async ensureUploadDir() {
    try {
      await fs.mkdir(UPLOAD_DIR, { recursive: true, mode: 0o750 });
    } catch (err) {
      if (err.code !== 'EEXIST') throw err;
    }
  }
  
  async uploadFile(file, options = {}) {
    const {
      allowedTypes = Object.keys(ALLOWED_MIME_TYPES),
      maxSize = MAX_FILE_SIZE,
      generateFilename = true,
      userId = 'anonymous'
    } = options;
    
    // Validate file presence
    if (!file || !file.buffer) {
      throw new Error('No file provided');
    }
    
    // Validate file size
    if (file.buffer.length > maxSize) {
      throw new Error(`File size exceeds maximum of ${maxSize / 1024 / 1024}MB`);
    }
    
    // Get file extension
    const originalName = file.originalname || 'unknown';
    const ext = path.extname(originalName).toLowerCase();
    
    // Check dangerous extensions
    if (DANGEROUS_EXTENSIONS.includes(ext)) {
      throw new Error(`File type ${ext} is not allowed`);
    }
    
    // Detect MIME type from magic bytes
    const detectedMimeType = await this.detectMimeType(file.buffer);
    
    if (!allowedTypes.includes(detectedMimeType)) {
      throw new Error(`File type ${detectedMimeType} is not allowed`);
    }
    
    // Validate extension matches MIME type
    if (!this.validateExtensionMimeMatch(detectedMimeType, ext)) {
      throw new Error('File extension does not match detected file type');
    }
    
    // Generate secure filename
    let filename;
    if (generateFilename) {
      const timestamp = Date.now();
      const randomSuffix = crypto.randomBytes(8).toString('hex');
      const safeOriginalName = originalName.replace(/[^a-zA-Z0-9.-]/g, '_').substring(0, 100);
      filename = `${timestamp}-${randomSuffix}-${safeOriginalName}`;
    } else {
      filename = originalName;
    }
    
    // Sanitize filename
    filename = this.sanitizeFilename(filename);
    
    // Get user directory
    const userDir = path.join(UPLOAD_DIR, userId);
    await fs.mkdir(userDir, { recursive: true, mode: 0o750 });
    
    // Final path
    const filePath = path.join(userDir, filename);
    
    // Write file
    await fs.writeFile(filePath, file.buffer, { mode: 0o640 });
    
    // Return file info
    return {
      filename,
      originalName,
      path: filePath,
      size: file.buffer.length,
      mimeType: detectedMimeType,
      extension: ext,
      checksum: crypto.createHash('sha256').update(file.buffer).digest('hex')
    };
  }
  
  async detectMimeType(buffer) {
    const header = buffer.slice(0, 16).toString('hex').toUpperCase();
    
    for (const [mimeType, signatures] of Object.entries(MAGIC_BYTES)) {
      for (const sig of signatures) {
        const cleanSig = sig.replace(/\s/g, '');
        if (header.startsWith(cleanSig)) {
          return mimeType;
        }
      }
    }
    
    return 'application/octet-stream';
  }
  
  validateExtensionMimeMatch(mimeType, extension) {
    const allowedExtensions = ALLOWED_MIME_TYPES[mimeType] || [];
    return allowedExtensions.includes(extension);
  }
  
  sanitizeFilename(filename) {
    return filename
      .replace(/[^a-zA-Z0-9._-]/g, '_')
      .replace(/\.{2,}/g, '.')
      .replace(/^_+/g, '')
      .replace(/_+$/g, '')
      .substring(0, 200);
  }
  
  async validateUploadedFile(filePath) {
    const stats = await fs.stat(filePath);
    
    // Check file size
    if (stats.size > MAX_FILE_SIZE) {
      throw new Error('File size exceeds limit');
    }
    
    // Check if file exists
    if (!await this.fileExists(filePath)) {
      throw new Error('File not found');
    }
    
    // Read and validate content
    const buffer = await fs.readFile(filePath);
    const mimeType = await this.detectMimeType(buffer);
    
    // Check for malicious content
    if (await this.containsMaliciousContent(buffer)) {
      await fs.unlink(filePath);
      throw new Error('File contains malicious content');
    }
    
    return {
      valid: true,
      mimeType,
      size: stats.size
    };
  }
  
  async containsMaliciousContent(buffer) {
    // Check for PHP tags
    if (buffer.includes('<?php') || buffer.includes('<?=') || buffer.includes('<%')) {
      return true;
    }
    
    // Check for shell scripts
    if (buffer.includes('#!/bin/bash') || buffer.includes('#!/bin/sh')) {
      return true;
    }
    
    // Check for null bytes
    if (buffer.includes('\0')) {
      return true;
    }
    
    // Check for executable headers (MZ for Windows executables)
    if (buffer.length > 2 && buffer[0] === 0x4D && buffer[1] === 0x5A) {
      return true;
    }
    
    return false;
  }
  
  async fileExists(filePath) {
    try {
      await fs.access(filePath, fs.constants.F_OK);
      return true;
    } catch {
      return false;
    }
  }
}

module.exports = new SecureFileUploader();
Step 4.1.2: File Upload Route with Protection
// src/routes/upload.js
const express = require('express');
const multer = require('multer');
const { requireUserAuth } = require('../middleware/auth');
const fileUploader = require('../utils/fileUploader');
const { auditLog } = require('../middleware/auditLogger');
const crypto = require('crypto');

const router = express.Router();

// Configure multer for memory storage
const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 50 * 1024 * 1024, // 50MB
    files: 5
  },
  fileFilter: (req, file, cb) => {
    const allowedMimeTypes = [
      'image/jpeg', 'image/png', 'image/gif', 'image/webp',
      'application/pdf',
      'text/plain', 'text/markdown',
      'application/zip'
    ];
    
    if (!allowedMimeTypes.includes(file.mimetype)) {
      return cb(new Error(`File type ${file.mimetype} is not allowed`));
    }
    
    cb(null, true);
  }
});

// Upload single file
router.post('/single', requireUserAuth, upload.single('file'), async (req, res) => {
  try {
    if (!req.file) {
      return res.status(400).json({ error: 'No file uploaded' });
    }
    
    const fileInfo = await fileUploader.uploadFile(req.file, {
      userId: req.user.id
    });
    
    await auditLog(req, 'FILE_UPLOAD', 'file', fileInfo.checksum, null, {
      filename: fileInfo.originalName,
      size: fileInfo.size,
      mimeType: fileInfo.mimeType
    });
    
    res.json({
      success: true,
      file: fileInfo
    });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Upload multiple files
router.post('/multiple', requireUserAuth, upload.array('files', 5), async (req, res) => {
  try {
    if (!req.files || req.files.length === 0) {
      return res.status(400).json({ error: 'No files uploaded' });
    }
    
    const uploadedFiles = [];
    
    for (const file of req.files) {
      const fileInfo = await fileUploader.uploadFile(file, {
        userId: req.user.id
      });
      uploadedFiles.push(fileInfo);
    }
    
    await auditLog(req, 'MULTIPLE_FILES_UPLOAD', 'files', req.user.id, null, {
      count: uploadedFiles.length,
      files: uploadedFiles.map(f => f.filename)
    });
    
    res.json({
      success: true,
      files: uploadedFiles
    });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Download file (with access control)
router.get('/:filename', requireUserAuth, async (req, res) => {
  try {
    const { filename } = req.params;
    
    // Validate filename
    if (!filename || filename.includes('..') || filename.includes('/')) {
      return res.status(400).json({ error: 'Invalid filename' });
    }
    
    const filePath = path.join(UPLOAD_DIR, req.user.id, filename);
    
    if (!await fileUploader.fileExists(filePath)) {
      return res.status(404).json({ error: 'File not found' });
    }
    
    // Validate file
    await fileUploader.validateUploadedFile(filePath);
    
    // Send file
    res.download(filePath, filename);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

// Delete file
router.delete('/:filename', requireUserAuth, async (req, res) => {
  try {
    const { filename } = req.params;
    
    // Validate filename
    if (!filename || filename.includes('..') || filename.includes('/')) {
      return res.status(400).json({ error: 'Invalid filename' });
    }
    
    const filePath = path.join(UPLOAD_DIR, req.user.id, filename);
    
    if (!await fileUploader.fileExists(filePath)) {
      return res.status(404).json({ error: 'File not found' });
    }
    
    // Delete file
    await fs.unlink(filePath);
    
    await auditLog(req, 'FILE_DELETE', 'file', filename);
    
    res.json({ success: true, message: 'File deleted' });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

module.exports = router;

Verification Steps

  1. Test file type validation
  2. Test magic byte detection
  3. Test dangerous extension blocking
  4. Verify file content scanning
  5. Test upload limits

Phase 5: Security Headers & Infrastructure

5.1 Comprehensive Security Headers Implementation

Priority: High
Estimated Effort: 8 hours
Risk Level: Low

Remediation Steps

Step 5.1.1: Enhanced Security Headers Configuration
// src/config/securityHeaders.js
const helmet = require('helmet');

function createSecurityHeadersConfig() {
  return {
    // Content Security Policy
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: [
          "'self'",
          "'unsafe-inline'", // Required for inline scripts - consider refactoring
          "https://cdn.jsdelivr.net",
          "https://apis.google.com"
        ],
        styleSrc: [
          "'self'",
          "'unsafe-inline'",
          "https://fonts.googleapis.com",
          "https://cdn.jsdelivr.net"
        ],
        fontSrc: [
          "'self'",
          "https://fonts.gstatic.com",
          "https://cdn.jsdelivr.net"
        ],
        imgSrc: [
          "'self'",
          "data:",
          "https:",
          "blob:"
        ],
        connectSrc: [
          "'self'",
          "https://api.openrouter.ai",
          "https://api.mistral.ai",
          "https://api.groq.com"
        ],
        frameSrc: [
          "'self'",
          "https://www.youtube.com",
          "https://player.vimeo.com"
        ],
        objectSrc: ["'none'"],
        mediaSrc: ["'self'", "blob:"],
        workerSrc: ["'self'", "blob:"],
        manifestSrc: ["'self'"],
        prefetchSrc: ["'self'"],
        baseUri: ["'self'"],
        formAction: ["'self'"],
        upgradeInsecureRequests: []
      }
    },
    
    // Prevent clickjacking
    crossOriginEmbedderPolicy: false,
    
    // Referrer policy
    referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
    
    // DNS prefetch control
    dnsPrefetchControl: { allow: false },
    
    // IE compatibility
    xContentTypeOptions: true,
    
    // Frame blocking
    frameguard: { action: 'deny' },
    
    // Power status
    powersaveBlock: false,
    
    // Cross-origin policies
    crossOriginResourcePolicy: { policy: 'cross-origin' },
    
    // Cross-origin opener policy
    crossOriginOpenerPolicy: { policy: 'same-origin' },
    
    // Permissions policy
    permissionsPolicy: {
      features: {
        accelerometer: [],
        camera: [],
        geolocation: [],
        gyroscope: [],
        magnetometer: [],
        microphone: [],
        payment: [],
        usb: []
      }
    }
  };
}

module.exports = { createSecurityHeadersConfig };
Step 5.1.2: Security Headers Middleware
// src/middleware/securityHeaders.js
const { createSecurityHeadersConfig } = require('../config/securityHeaders');

function securityHeaders(req, res, next) {
  // Prevent information disclosure
  res.removeHeader('X-Powered-By');
  
  // Basic security headers
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
  
  // Cache control for sensitive pages
  if (req.path.includes('/account') || req.path.includes('/admin')) {
    res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');
  }
  
  next();
}

module.exports = { securityHeaders };

Verification Steps

  1. Test all security headers with curl
  2. Verify CSP blocks XSS attempts
  3. Test clickjacking protection
  4. Verify headers don't break functionality

5.2 Docker Security Hardening

Priority: High
Estimated Effort: 12 hours
Risk Level: Medium

Remediation Steps

Step 5.2.1: Enhanced Dockerfile
# Enhanced Dockerfile with security hardening
FROM ubuntu:24.04 AS base

# Security: Set environment variables
ENV DEBIAN_FRONTEND=noninteractive
ENV NODE_ENV=production
ENV NPM_CONFIG_LOGLEVEL=warn

# Security: Create non-root user
RUN groupadd --gid 1000 appgroup && \
    useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser

# Security: Install only necessary packages
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        ca-certificates \
        curl \
        gnupg \
        logrotate \
        net-tools \
        openssl \
        powershell-7.4 \
        ttdl \
        unzip \
        wget \
        zip \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# Security: Install Node.js from official package
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && npm install -g npm@latest \
    && npm config set audit false \
    && npm config set fund false

# Security: Set working directory
WORKDIR /home/web

# Security: Copy application with correct permissions
COPY --chown=appuser:appgroup package*.json ./
COPY --chown=appuser:appgroup chat ./chat/
COPY --chown=appuser:appgroup scripts ./scripts/
COPY --chown=appuser:appgroup opencode ./opencode/

# Security: Install dependencies as non-root user
USER appuser

WORKDIR /home/web/chat

# Install Node.js dependencies
RUN npm ci --only=production && \
    npm cache clean --force

# Security: Set environment variables
ENV PORT=4500
ENV HOST=0.0.0.0
ENV NODE_ENV=production

# Expose application port
EXPOSE 4500

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:4500/health || exit 1

# Security: Use non-root user to run application
USER appuser

# Run application
CMD ["node", "server.js"]
Step 5.2.2: Enhanced Docker Compose
# docker-compose.yml - Security enhanced version
version: '3.8'

services:
  shopify-ai:
    build: .
    container_name: shopify-ai-secure
    restart: unless-stopped
    ports:
      - "4500:4500"
    environment:
      - NODE_ENV=production
      - PORT=4500
      - HOST=0.0.0.0
      - SESSION_SECRET=${SESSION_SECRET:?err}
      - JWT_SECRET=${JWT_SECRET:?err}
      - DATABASE_ENCRYPTION_KEY=${DATABASE_ENCRYPTION_KEY:?err}
      - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:?err}
      - MISTRAL_API_KEY=${MISTRAL_API_KEY:?err}
      - GROQ_API_KEY=${GROQ_API_KEY:?err}
      - DODO_PAYMENTS_API_KEY=${DODO_PAYMENTS_API_KEY}
      - DODO_PAYMENTS_WEBHOOK_KEY=${DODO_PAYMENTS_WEBHOOK_KEY}
      - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
      - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
      - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
      - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_PORT=${SMTP_PORT}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
      - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost:4500}
    volumes:
      - app-data:/home/web/data
      - app-logs:/home/web/logs
      - app-uploads:/home/web/data/uploads
    deploy:
      resources:
        limits:
          memory: 1.5G
          cpus: '1.0'
        reservations:
          memory: 512M
          cpus: '0.25'
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp:size=10M,mode=1777
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4500/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

volumes:
  app-data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ${DATA_PATH:-./data}
  app-logs:
    driver: local
  app-uploads:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: ${UPLOADS_PATH:-./uploads}

secrets:
  session_secret:
    file: ./secrets/session_secret.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt

Verification Steps

  1. Build and test Docker image
  2. Verify non-root user
  3. Test security constraints
  4. Verify resource limits

Phase 6: Logging & Monitoring Enhancement

6.1 Comprehensive Security Logging

Priority: Medium
Estimated Effort: 12 hours
Risk Level: Low

Remediation Steps

Step 6.1.1: Security Event Logger
// src/utils/securityLogger.js
const winston = require('winston');
const path = require('path');

const LOG_DIR = process.env.LOG_DIR || '/home/web/logs';

// Custom format for security events
const securityFormat = winston.format.combine(
  winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
  winston.format.errors({ stack: true }),
  winston.format.json()
);

// Security event logger
const securityLogger = winston.createLogger({
  level: 'info',
  format: securityFormat,
  defaultMeta: { service: 'shopify-ai-security' },
  transports: [
    // Error logs
    new winston.transports.File({
      filename: path.join(LOG_DIR, 'security-errors.log'),
      level: 'error',
      maxsize: 10485760, // 10MB
      maxFiles: 10,
      tailable: true
    }),
    // All security events
    new winston.transports.File({
      filename: path.join(LOG_DIR, 'security-events.log'),
      maxsize: 10485760, // 10MB
      maxFiles: 30,
      tailable: true
    }),
    // Failed authentication attempts
    new winston.transports.File({
      filename: path.join(LOG_DIR, 'auth-failures.log'),
      level: 'warn',
      maxsize: 10485760,
      maxFiles: 100,
      tailable: true
    }),
    // Audit log for compliance
    new winston.transports.File({
      filename: path.join(LOG_DIR, 'audit.log'),
      level: 'info',
      maxsize: 10485760,
      maxFiles: 365, // Keep 1 year
      tailable: true
    })
  ]
});

// Add console transport in development
if (process.env.NODE_ENV !== 'production') {
  securityLogger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    )
  }));
}

// Security event types
const SecurityEvents = {
  // Authentication events
  LOGIN_SUCCESS: 'AUTH_LOGIN_SUCCESS',
  LOGIN_FAILURE: 'AUTH_LOGIN_FAILURE',
  LOGOUT: 'AUTH_LOGOUT',
  PASSWORD_CHANGE: 'AUTH_PASSWORD_CHANGE',
  PASSWORD_RESET_REQUEST: 'AUTH_PASSWORD_RESET_REQUEST',
  PASSWORD_RESET_COMPLETE: 'AUTH_PASSWORD_RESET_COMPLETE',
  ACCOUNT_LOCKED: 'AUTH_ACCOUNT_LOCKED',
  ACCOUNT_UNLOCKED: 'AUTH_ACCOUNT_UNLOCKED',
  TWO_FACTOR_ENABLED: 'AUTH_2FA_ENABLED',
  TWO_FACTOR_DISABLED: 'AUTH_2FA_DISABLED',
  SESSION_CREATED: 'AUTH_SESSION_CREATED',
  SESSION_REVOKED: 'AUTH_SESSION_REVOKED',
  
  // Authorization events
  UNAUTHORIZED_ACCESS: 'AUTH_UNAUTHORIZED_ACCESS',
  FORBIDDEN_ACCESS: 'AUTH_FORBIDDEN_ACCESS',
  ADMIN_ACTION: 'AUTH_ADMIN_ACTION',
  
  // Data events
  DATA_EXPORT: 'DATA_EXPORT',
  DATA_DELETE: 'DATA_DELETE',
  DATA_UPDATE: 'DATA_UPDATE',
  FILE_UPLOAD: 'FILE_UPLOAD',
  FILE_DOWNLOAD: 'FILE_DOWNLOAD',
  FILE_DELETE: 'FILE_DELETE',
  
  // Security events
  RATE_LIMIT_EXCEEDED: 'SEC_RATE_LIMIT_EXCEEDED',
  SUSPICIOUS_ACTIVITY: 'SEC_SUSPICIOUS_ACTIVITY',
  PROMPT_INJECTION: 'SEC_PROMPT_INJECTION',
  XSS_ATTEMPT: 'SEC_XSS_ATTEMPT',
  SQL_INJECTION_ATTEMPT: 'SEC_SQL_INJECTION_ATTEMPT',
  FILE_UPLOAD_BLOCKED: 'SEC_FILE_UPLOAD_BLOCKED',
  
  // System events
  CONFIG_CHANGE: 'SYS_CONFIG_CHANGE',
  API_KEY_ACCESS: 'SYS_API_KEY_ACCESS'
};

// Log security event
function logSecurityEvent(eventType, metadata = {}) {
  const event = {
    eventType,
    timestamp: new Date().toISOString(),
    ...metadata
  };
  
  // Determine log level based on event type
  let level = 'info';
  if (eventType.startsWith('AUTH_FAILURE') || eventType.startsWith('SEC_')) {
    level = 'warn';
  }
  if (eventType.includes('ERROR')) {
    level = 'error';
  }
  
  securityLogger.log(level, event);
  
  return event;
}

// Log authentication event
function logAuthEvent(eventType, userId, req, additionalData = {}) {
  logSecurityEvent(eventType, {
    userId,
    ip: req.ip || req.headers['x-forwarded-for'],
    userAgent: req.headers['user-agent'],
    path: req.path,
    method: req.method,
    ...additionalData
  });
}

module.exports = {
  securityLogger,
  SecurityEvents,
  logSecurityEvent,
  logAuthEvent
};
Step 6.1.2: Security Monitoring Middleware
// src/middleware/securityMonitoring.js
const { logSecurityEvent, SecurityEvents } = require('../utils/securityLogger');

function securityMonitoring(req, res, next) {
  const startTime = Date.now();
  
  // Capture original end function
  const originalEnd = res.end;
  let responseBody;
  
  res.end = function(chunk, encoding) {
    responseBody = chunk;
    return originalEnd.apply(this, arguments);
  };
  
  res.on('finish', () => {
    const duration = Date.now() - startTime;
    
    // Log suspicious response patterns
    if (res.statusCode === 404 && req.path.startsWith('/api/')) {
      logSecurityEvent(SecurityEvents.SUSPICIOUS_ACTIVITY, {
        userId: req.user?.id || 'anonymous',
        ip: req.ip,
        path: req.path,
        method: req.method,
        reason: '404 on API endpoint'
      });
    }
    
    // Log slow requests (potential DoS)
    if (duration > 10000 && req.path.startsWith('/api/')) {
      logSecurityEvent(SecurityEvents.SUSPICIOUS_ACTIVITY, {
        userId: req.user?.id || 'anonymous',
        ip: req.ip,
        path: req.path,
        method: req.method,
        duration,
        reason: 'Slow request detected'
      });
    }
  });
  
  next();
}

module.exports = { securityMonitoring };

Verification Steps

  1. Verify all security events are logged
  2. Test log rotation
  3. Verify log integrity
  4. Test alert generation

Phase 7: API Security Enhancement

7.1 Enhanced Rate Limiting with Fingerprinting

Priority: Medium
Estimated Effort: 8 hours
Risk Level: Low

Remediation Steps

Step 7.1.1: Advanced Rate Limiter
// src/middleware/advancedRateLimiter.js
const rateLimit = require('express-rate-limit');
const crypto = require('crypto');

function createAdvancedRateLimiter(options = {}) {
  const {
    windowMs = 60 * 1000,
    max = 100,
    message = 'Too many requests',
    keyGenerator = null
  } = options;
  
  // Store for rate limiting (use Redis in production)
  const rateLimitStore = new Map();
  
  const limiter = rateLimit({
    windowMs,
    max,
    message: {
      error: message,
      retryAfter: Math.ceil(windowMs / 1000)
    },
    standardHeaders: true,
    legacyHeaders: false,
    
    keyGenerator: (req) => {
      // Create fingerprint combining IP and User-Agent
      if (keyGenerator) {
        return keyGenerator(req);
      }
      
      const userAgent = req.headers['user-agent'] || 'unknown';
      const ip = req.ip || req.headers['x-forwarded-for'] || 'unknown';
      
      const fingerprint = crypto
        .createHash('sha256')
        .update(`${ip}:${userAgent}`)
        .digest('hex')
        .substring(0, 32);
      
      return fingerprint;
    },
    
    skip: (req) => {
      // Skip for health checks
      return req.path === '/health' || req.path === '/api/health';
    },
    
    handler: (req, res, options) => {
      // Log rate limit violations
      logSecurityEvent(SecurityEvents.RATE_LIMIT_EXCEEDED, {
        userId: req.user?.id || 'anonymous',
        ip: req.ip,
        userAgent: req.headers['user-agent'],
        path: req.path,
        method: req.method
      });
      
      res.status(429).json({
        error: options.message,
        retryAfter: options.message.retryAfter
      });
    }
  });
  
  return limiter;
}

// Specialized rate limiters
const generalRateLimiter = createAdvancedRateLimiter({
  windowMs: 60 * 1000,
  max: 100
});

const authRateLimiter = createAdvancedRateLimiter({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Too many authentication attempts'
});

const apiRateLimiter = createAdvancedRateLimiter({
  windowMs: 60 * 1000,
  max: 60
});

const uploadRateLimiter = createAdvancedRateLimiter({
  windowMs: 60 * 60 * 1000,
  max: 20,
  message: 'Too many file uploads'
});

module.exports = {
  createAdvancedRateLimiter,
  generalRateLimiter,
  authRateLimiter,
  apiRateLimiter,
  uploadRateLimiter
};

Verification Steps

  1. Test rate limiting thresholds
  2. Verify fingerprint-based limiting
  3. Test bypass attempts
  4. Check 429 response format

Phase 8: Compliance & Documentation

8.1 Security Documentation Update

Priority: Medium
Estimated Effort: 8 hours
Risk Level: Low

Remediation Steps

Step 8.1.1: Update Security Documentation

Create comprehensive security documentation including:

  1. Incident Response Plan
  2. Security Architecture Document
  3. Encryption Key Management Policy
  4. Access Control Matrix
  5. Security Audit Procedures
  6. Vulnerability Disclosure Policy
Step 8.1.2: Security Runbook

Document procedures for:

  1. Responding to security incidents
  2. Revoking compromised credentials
  3. Handling data breaches
  4. Password reset procedures
  5. Account recovery processes

Implementation Timeline

Phase 1: Critical Security (Weeks 1-2)

Task Effort Priority Dependencies
Express.js Framework Migration 40h Critical None
Database with Encryption 60h Critical None
Session Revocation 20h Critical Database

Phase 2: Authentication (Weeks 2-3)

Task Effort Priority Dependencies
Password Enhancement 16h High None
Two-Factor Authentication 24h High Database

Phase 3: Input Validation (Weeks 3-4)

Task Effort Priority Dependencies
Sanitization Framework 20h High None
AI Prompt Protection 16h High None

Phase 4: File Security (Week 4)

Task Effort Priority Dependencies
Secure File Upload 16h High None

Phase 5: Infrastructure (Weeks 5-6)

Task Effort Priority Dependencies
Security Headers 8h High None
Docker Hardening 12h High None

Phase 6-8: Logging & Compliance (Weeks 6-8)

Task Effort Priority Dependencies
Security Logging 12h Medium None
Rate Limiting 8h Medium None
Documentation 8h Medium All phases

Total Effort Estimate

Phase Hours
Phase 1: Critical Infrastructure 120h
Phase 2: Authentication Security 40h
Phase 3: Input Validation 36h
Phase 4: File Upload Security 16h
Phase 5: Infrastructure Security 20h
Phase 6: Logging & Monitoring 12h
Phase 7: API Security 8h
Phase 8: Documentation 8h
Total 260 hours

Testing Requirements

Security Testing Checklist

  1. Penetration testing on all endpoints
  2. Fuzz testing for input validation
  3. Load testing with rate limits
  4. Authentication flow testing
  5. File upload vulnerability testing
  6. Session management testing
  7. API security testing
  8. Compliance verification

Automated Security Testing

  1. Integrate npm audit into CI/CD
  2. Implement SAST scanning
  3. Configure dependency vulnerability scanning
  4. Add security headers validation to tests
  5. Implement security regression testing

Rollback Procedures

Each phase should include:

  1. Database backups before changes
  2. Configuration snapshots
  3. Feature flags for gradual rollout
  4. Immediate rollback procedures
  5. Communication plan for users

Approval Required

This security remediation plan requires:

  • Security team review
  • Development team approval
  • Operations team sign-off
  • Management budget approval (~$260,000 based on 260 hours)
  • Compliance team acknowledgment

Document Control

Version Date Author Changes
1.0 2026-02-08 Security Team Initial plan

End of Document