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

3149 lines
83 KiB
Markdown

# 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
```bash
# 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)
```javascript
// 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)
```javascript
// 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
```bash
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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```javascript
// 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
```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
```yaml
# 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
```javascript
// 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
```javascript
// 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
```javascript
// 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**