Security Best Practices
Implement comprehensive security measures to protect your Azure AI Foundry applications, user data, and AI models from threats while maintaining compliance with security standards and regulations.Security fundamentals
Defense in depth
Implement multiple layers of security:- Network security - Virtual networks, firewalls, private endpoints
- Identity and access - Authentication, authorization, RBAC
- Application security - Input validation, output sanitization, secure coding
- Data protection - Encryption at rest and in transit, data classification
- Monitoring and response - Threat detection, incident response, audit logging
Zero Trust principles
- Verify explicitly - Always authenticate and authorize
- Use least privilege access - Minimize access rights for users and applications
- Assume breach - Segment access and verify end-to-end encryption
Identity and access management
Azure Active Directory integration
Copy
const { AuthenticationResult, PublicClientApplication } = require('@azure/msal-node');
const { Client } = require('@microsoft/microsoft-graph-client');
const msalConfig = {
auth: {
clientId: process.env.AZURE_CLIENT_ID,
authority: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}`,
clientSecret: process.env.AZURE_CLIENT_SECRET
}
};
const msalInstance = new PublicClientApplication(msalConfig);
// Middleware for token validation
async function validateAzureADToken(req, res, next) {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}
const token = authHeader.substring(7);
// Validate token with Azure AD
const decodedToken = await validateJWT(token);
// Check required scopes
const requiredScopes = ['ai.chat.use'];
const userScopes = decodedToken.scp ? decodedToken.scp.split(' ') : [];
const hasRequiredScope = requiredScopes.some(scope => userScopes.includes(scope));
if (!hasRequiredScope) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
req.user = {
id: decodedToken.oid,
email: decodedToken.preferred_username,
name: decodedToken.name,
roles: decodedToken.roles || [],
scopes: userScopes
};
next();
} catch (error) {
console.error('Token validation failed:', error);
return res.status(401).json({ error: 'Invalid token' });
}
}
async function validateJWT(token) {
// Implement JWT validation against Azure AD
// This would typically use a library like jsonwebtoken
// with Azure AD's public keys for verification
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');
const client = jwksClient({
jwksUri: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/discovery/v2.0/keys`
});
function getKey(header, callback) {
client.getSigningKey(header.kid, (err, key) => {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, {
audience: process.env.AZURE_CLIENT_ID,
issuer: `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/v2.0`,
algorithms: ['RS256']
}, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
});
}
// Apply authentication to protected routes
app.use('/api/', validateAzureADToken);
Role-based access control (RBAC)
Copy
// Define roles and permissions
const roles = {
'Admin': [
'ai.models.deploy',
'ai.models.manage',
'ai.chat.use',
'ai.fine-tune.create',
'users.manage'
],
'Developer': [
'ai.models.deploy',
'ai.chat.use',
'ai.fine-tune.create'
],
'User': [
'ai.chat.use'
]
};
function hasPermission(userRoles, requiredPermission) {
return userRoles.some(role =>
roles[role] && roles[role].includes(requiredPermission)
);
}
// Permission middleware
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!hasPermission(req.user.roles, permission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Usage in routes
app.post('/api/models/deploy',
requirePermission('ai.models.deploy'),
async (req, res) => {
// Deploy model logic
}
);
app.post('/api/chat',
requirePermission('ai.chat.use'),
async (req, res) => {
// Chat logic
}
);
API key management
Copy
const crypto = require('crypto');
const bcrypt = require('bcrypt');
class APIKeyManager {
constructor() {
this.saltRounds = 12;
}
generateAPIKey() {
// Generate a secure API key
const prefix = 'aif_'; // Azure AI Foundry prefix
const randomBytes = crypto.randomBytes(32);
const key = prefix + randomBytes.toString('base64url');
return key;
}
async hashAPIKey(apiKey) {
// Hash the API key for storage
return await bcrypt.hash(apiKey, this.saltRounds);
}
async validateAPIKey(providedKey, hashedKey) {
// Validate provided key against stored hash
return await bcrypt.compare(providedKey, hashedKey);
}
async createAPIKey(userId, name, permissions = []) {
const apiKey = this.generateAPIKey();
const hashedKey = await this.hashAPIKey(apiKey);
const keyRecord = {
id: crypto.randomUUID(),
userId: userId,
name: name,
hashedKey: hashedKey,
permissions: permissions,
createdAt: new Date(),
lastUsed: null,
isActive: true
};
// Store in database
await this.storeAPIKey(keyRecord);
// Return the unhashed key only once
return {
apiKey: apiKey,
keyId: keyRecord.id,
permissions: permissions
};
}
async revokeAPIKey(keyId, userId) {
// Mark API key as inactive
await this.updateAPIKey(keyId, {
isActive: false,
revokedAt: new Date(),
revokedBy: userId
});
}
}
// API key validation middleware
const apiKeyManager = new APIKeyManager();
async function validateAPIKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
try {
const keyRecord = await findAPIKeyRecord(apiKey);
if (!keyRecord || !keyRecord.isActive) {
return res.status(401).json({ error: 'Invalid or inactive API key' });
}
const isValid = await apiKeyManager.validateAPIKey(apiKey, keyRecord.hashedKey);
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Update last used timestamp
await updateAPIKeyLastUsed(keyRecord.id);
req.apiKey = {
id: keyRecord.id,
userId: keyRecord.userId,
permissions: keyRecord.permissions
};
next();
} catch (error) {
console.error('API key validation error:', error);
return res.status(500).json({ error: 'Authentication service error' });
}
}
Input validation and sanitization
Comprehensive input validation
Copy
const { body, param, query, validationResult } = require('express-validator');
const DOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const purify = DOMPurify(window);
// Validation rules for chat messages
const chatValidation = [
body('message')
.isLength({ min: 1, max: 8000 })
.withMessage('Message must be between 1 and 8000 characters')
.trim()
.custom((value) => {
// Check for potential injection attempts
const suspiciousPatterns = [
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/data:text\/html/gi
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(value)) {
throw new Error('Message contains potentially malicious content');
}
}
return true;
}),
body('conversationId')
.optional()
.isUUID()
.withMessage('Invalid conversation ID format'),
body('model')
.optional()
.isIn(['gpt-4o', 'gpt-4o-mini', 'gpt-35-turbo'])
.withMessage('Invalid model specified'),
body('temperature')
.optional()
.isFloat({ min: 0, max: 2 })
.withMessage('Temperature must be between 0 and 2'),
body('maxTokens')
.optional()
.isInt({ min: 1, max: 4000 })
.withMessage('Max tokens must be between 1 and 4000')
];
// Sanitization function
function sanitizeInput(input) {
if (typeof input !== 'string') {
return input;
}
// Remove HTML tags and encode entities
let sanitized = purify.sanitize(input, { ALLOWED_TAGS: [] });
// Additional sanitization for AI prompts
sanitized = sanitized
.replace(/[\x00-\x1F\x7F]/g, '') // Remove control characters
.replace(/\s+/g, ' ') // Normalize whitespace
.trim();
return sanitized;
}
// Apply validation and sanitization
app.post('/api/chat', chatValidation, async (req, res) => {
// Check validation results
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
// Sanitize inputs
const sanitizedMessage = sanitizeInput(req.body.message);
// Additional content filtering
if (await isContentInappropriate(sanitizedMessage)) {
return res.status(400).json({
error: 'Content violates usage policies'
});
}
// Process the request with sanitized input
const { conversationId, model = 'gpt-4o' } = req.body;
// ... rest of the handler
});
async function isContentInappropriate(content) {
// Implement content filtering logic
// This could integrate with Azure Content Safety or other content moderation services
const inappropriatePatterns = [
/\b(hate|violence|harassment)\b/gi,
// Add more patterns as needed
];
return inappropriatePatterns.some(pattern => pattern.test(content));
}
SQL injection prevention
Copy
const { Pool } = require('pg');
// Use parameterized queries
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});
class ConversationService {
async getConversation(conversationId, userId) {
// Good: Parameterized query
const query = `
SELECT id, messages, created_at, updated_at
FROM conversations
WHERE id = $1 AND user_id = $2
`;
const result = await pool.query(query, [conversationId, userId]);
return result.rows[0];
}
async saveMessage(conversationId, userId, message, response) {
const client = await pool.connect();
try {
await client.query('BEGIN');
// Insert message
const messageQuery = `
INSERT INTO messages (conversation_id, user_id, content, role, created_at)
VALUES ($1, $2, $3, $4, NOW())
RETURNING id
`;
const userMessage = await client.query(messageQuery, [
conversationId, userId, message, 'user'
]);
const assistantMessage = await client.query(messageQuery, [
conversationId, userId, response, 'assistant'
]);
// Update conversation timestamp
const updateQuery = `
UPDATE conversations
SET updated_at = NOW()
WHERE id = $1 AND user_id = $2
`;
await client.query(updateQuery, [conversationId, userId]);
await client.query('COMMIT');
return {
userMessageId: userMessage.rows[0].id,
assistantMessageId: assistantMessage.rows[0].id
};
} catch (error) {
await client.query('ROLLBACK');
throw error;
} finally {
client.release();
}
}
}
Data protection
Encryption at rest and in transit
Copy
const crypto = require('crypto');
class DataEncryption {
constructor() {
this.algorithm = 'aes-256-gcm';
this.keyLength = 32;
this.ivLength = 16;
this.tagLength = 16;
// Use Azure Key Vault for key management in production
this.encryptionKey = this.getEncryptionKey();
}
getEncryptionKey() {
// In production, retrieve from Azure Key Vault
if (process.env.NODE_ENV === 'production') {
return this.getKeyFromKeyVault();
}
// For development, use environment variable
const key = process.env.ENCRYPTION_KEY;
if (!key) {
throw new Error('Encryption key not configured');
}
return Buffer.from(key, 'base64');
}
async getKeyFromKeyVault() {
const { SecretClient } = require("@azure/keyvault-secrets");
const { DefaultAzureCredential } = require("@azure/identity");
const credential = new DefaultAzureCredential();
const vaultName = process.env.KEY_VAULT_NAME;
const url = `https://${vaultName}.vault.azure.net`;
const client = new SecretClient(url, credential);
const secret = await client.getSecret('data-encryption-key');
return Buffer.from(secret.value, 'base64');
}
encrypt(plaintext) {
const iv = crypto.randomBytes(this.ivLength);
const cipher = crypto.createCipher(this.algorithm, this.encryptionKey, iv);
let encrypted = cipher.update(plaintext, 'utf8');
encrypted = Buffer.concat([encrypted, cipher.final()]);
const tag = cipher.getAuthTag();
// Combine IV, tag, and encrypted data
const result = Buffer.concat([iv, tag, encrypted]);
return result.toString('base64');
}
decrypt(encryptedData) {
const data = Buffer.from(encryptedData, 'base64');
const iv = data.slice(0, this.ivLength);
const tag = data.slice(this.ivLength, this.ivLength + this.tagLength);
const encrypted = data.slice(this.ivLength + this.tagLength);
const decipher = crypto.createDecipher(this.algorithm, this.encryptionKey, iv);
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf8');
}
}
const encryption = new DataEncryption();
// Encrypt sensitive data before storing
async function storeConversation(userId, conversationData) {
const encryptedData = encryption.encrypt(JSON.stringify(conversationData));
const query = `
INSERT INTO conversations (user_id, encrypted_data, created_at)
VALUES ($1, $2, NOW())
RETURNING id
`;
const result = await pool.query(query, [userId, encryptedData]);
return result.rows[0].id;
}
// Decrypt when retrieving
async function getConversation(conversationId, userId) {
const query = `
SELECT encrypted_data FROM conversations
WHERE id = $1 AND user_id = $2
`;
const result = await pool.query(query, [conversationId, userId]);
if (result.rows.length === 0) {
return null;
}
const decryptedData = encryption.decrypt(result.rows[0].encrypted_data);
return JSON.parse(decryptedData);
}
Personal data handling
Copy
// PII detection and handling
class PIIHandler {
constructor() {
this.piiPatterns = {
ssn: /\b\d{3}-?\d{2}-?\d{4}\b/g,
creditCard: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
ipAddress: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g
};
}
detectPII(text) {
const detected = [];
for (const [type, pattern] of Object.entries(this.piiPatterns)) {
const matches = text.match(pattern);
if (matches) {
detected.push({
type: type,
matches: matches,
count: matches.length
});
}
}
return detected;
}
sanitizePII(text, replaceWith = '[REDACTED]') {
let sanitized = text;
for (const pattern of Object.values(this.piiPatterns)) {
sanitized = sanitized.replace(pattern, replaceWith);
}
return sanitized;
}
async logPIIDetection(userId, conversationId, piiDetected) {
// Log PII detection for compliance and monitoring
const logEntry = {
userId: userId,
conversationId: conversationId,
piiTypes: piiDetected.map(p => p.type),
timestamp: new Date().toISOString(),
action: 'detected_and_sanitized'
};
await this.storePIILog(logEntry);
// Send alert for PII detection
await this.sendPIIAlert(logEntry);
}
async storePIILog(logEntry) {
const query = `
INSERT INTO pii_detection_logs
(user_id, conversation_id, pii_types, timestamp, action)
VALUES ($1, $2, $3, $4, $5)
`;
await pool.query(query, [
logEntry.userId,
logEntry.conversationId,
JSON.stringify(logEntry.piiTypes),
logEntry.timestamp,
logEntry.action
]);
}
async sendPIIAlert(logEntry) {
// Send alert to security team
console.warn('PII detected in conversation:', logEntry);
// Could integrate with Azure Security Center or other alerting systems
}
}
const piiHandler = new PIIHandler();
// Middleware to scan for PII
async function scanForPII(req, res, next) {
if (req.body.message) {
const piiDetected = piiHandler.detectPII(req.body.message);
if (piiDetected.length > 0) {
// Log the detection
await piiHandler.logPIIDetection(
req.user.id,
req.body.conversationId,
piiDetected
);
// Sanitize the message
req.body.message = piiHandler.sanitizePII(req.body.message);
// Add warning to response
req.piiWarning = {
detected: true,
types: piiDetected.map(p => p.type),
message: 'Personal information was detected and removed from your message.'
};
}
}
next();
}
// Apply PII scanning to chat endpoints
app.post('/api/chat', scanForPII, async (req, res) => {
// Process chat request
const response = await processChatRequest(req.body);
// Include PII warning if detected
if (req.piiWarning) {
response.warning = req.piiWarning;
}
res.json(response);
});
Network security
Virtual network configuration
Copy
#!/bin/bash
# Create resource group
az group create --name myapp-security-rg --location eastus
# Create virtual network
az network vnet create \
--resource-group myapp-security-rg \
--name myapp-vnet \
--address-prefix 10.0.0.0/16 \
--subnet-name app-subnet \
--subnet-prefix 10.0.1.0/24
# Create subnet for private endpoints
az network vnet subnet create \
--resource-group myapp-security-rg \
--vnet-name myapp-vnet \
--name private-endpoint-subnet \
--address-prefix 10.0.2.0/24
# Create network security group
az network nsg create \
--resource-group myapp-security-rg \
--name myapp-nsg
# Add security rules
az network nsg rule create \
--resource-group myapp-security-rg \
--nsg-name myapp-nsg \
--name AllowHTTPS \
--protocol Tcp \
--priority 1000 \
--destination-port-range 443 \
--access Allow \
--direction Inbound
az network nsg rule create \
--resource-group myapp-security-rg \
--nsg-name myapp-nsg \
--name DenyHTTP \
--protocol Tcp \
--priority 1100 \
--destination-port-range 80 \
--access Deny \
--direction Inbound
# Associate NSG with subnet
az network vnet subnet update \
--resource-group myapp-security-rg \
--vnet-name myapp-vnet \
--name app-subnet \
--network-security-group myapp-nsg
Private endpoints for AI services
Copy
# Create private endpoint for AI Foundry
az network private-endpoint create \
--resource-group myapp-security-rg \
--name ai-foundry-private-endpoint \
--vnet-name myapp-vnet \
--subnet private-endpoint-subnet \
--private-connection-resource-id "/subscriptions/{subscription-id}/resourceGroups/myapp-rg/providers/Microsoft.CognitiveServices/accounts/myapp-ai-foundry" \
--group-id account \
--connection-name ai-foundry-connection
# Create private DNS zone
az network private-dns zone create \
--resource-group myapp-security-rg \
--name privatelink.cognitiveservices.azure.com
# Link DNS zone to VNet
az network private-dns link vnet create \
--resource-group myapp-security-rg \
--zone-name privatelink.cognitiveservices.azure.com \
--name ai-foundry-dns-link \
--virtual-network myapp-vnet \
--registration-enabled false
Compliance and auditing
Audit logging
Copy
class AuditLogger {
constructor() {
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'audit.log' }),
new WinstonTransport({
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
})
]
});
}
logAccess(userId, resource, action, result, details = {}) {
this.logger.info('Access event', {
eventType: 'access',
userId: userId,
resource: resource,
action: action,
result: result,
timestamp: new Date().toISOString(),
details: details,
sessionId: details.sessionId,
ipAddress: details.ipAddress,
userAgent: details.userAgent
});
}
logDataAccess(userId, dataType, operation, recordCount, details = {}) {
this.logger.info('Data access event', {
eventType: 'data_access',
userId: userId,
dataType: dataType,
operation: operation,
recordCount: recordCount,
timestamp: new Date().toISOString(),
details: details
});
}
logSecurityEvent(eventType, severity, description, details = {}) {
this.logger.warn('Security event', {
eventType: 'security',
securityEventType: eventType,
severity: severity,
description: description,
timestamp: new Date().toISOString(),
details: details
});
}
logAIOperation(userId, model, operation, tokenCount, cost, success, details = {}) {
this.logger.info('AI operation', {
eventType: 'ai_operation',
userId: userId,
model: model,
operation: operation,
tokenCount: tokenCount,
cost: cost,
success: success,
timestamp: new Date().toISOString(),
details: details
});
}
}
const auditLogger = new AuditLogger();
// Audit middleware
function auditMiddleware(req, res, next) {
const originalSend = res.send;
res.send = function(data) {
// Log the request after processing
auditLogger.logAccess(
req.user?.id || 'anonymous',
req.path,
req.method,
res.statusCode < 400 ? 'success' : 'failure',
{
statusCode: res.statusCode,
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
sessionId: req.session?.id,
responseSize: Buffer.byteLength(data)
}
);
originalSend.call(this, data);
};
next();
}
app.use(auditMiddleware);
GDPR compliance
Copy
class GDPRHandler {
async handleDataExportRequest(userId) {
try {
// Collect all user data
const userData = await this.collectUserData(userId);
// Create export package
const exportData = {
user: userData.profile,
conversations: userData.conversations,
apiKeys: userData.apiKeys.map(key => ({
name: key.name,
created: key.createdAt,
lastUsed: key.lastUsed,
permissions: key.permissions
})),
auditLogs: userData.auditLogs,
exportedAt: new Date().toISOString()
};
// Log the export request
auditLogger.logDataAccess(
userId,
'user_data_export',
'export',
Object.keys(exportData).length,
{ requestType: 'gdpr_export' }
);
return exportData;
} catch (error) {
auditLogger.logSecurityEvent(
'data_export_failed',
'medium',
'Failed to export user data',
{ userId: userId, error: error.message }
);
throw error;
}
}
async handleDataDeletionRequest(userId) {
const client = await pool.connect();
try {
await client.query('BEGIN');
// Delete user conversations
await client.query('DELETE FROM conversations WHERE user_id = $1', [userId]);
// Delete API keys
await client.query('DELETE FROM api_keys WHERE user_id = $1', [userId]);
// Anonymize audit logs (keep for compliance but remove PII)
await client.query(
'UPDATE audit_logs SET user_id = $1 WHERE user_id = $2',
['deleted-user', userId]
);
// Delete user profile
await client.query('DELETE FROM users WHERE id = $1', [userId]);
await client.query('COMMIT');
auditLogger.logDataAccess(
userId,
'user_data_deletion',
'delete',
1,
{ requestType: 'gdpr_deletion' }
);
return { success: true, deletedAt: new Date().toISOString() };
} catch (error) {
await client.query('ROLLBACK');
auditLogger.logSecurityEvent(
'data_deletion_failed',
'high',
'Failed to delete user data',
{ userId: userId, error: error.message }
);
throw error;
} finally {
client.release();
}
}
async collectUserData(userId) {
const [profile, conversations, apiKeys, auditLogs] = await Promise.all([
this.getUserProfile(userId),
this.getUserConversations(userId),
this.getUserAPIKeys(userId),
this.getUserAuditLogs(userId)
]);
return { profile, conversations, apiKeys, auditLogs };
}
}
const gdprHandler = new GDPRHandler();
// GDPR endpoints
app.post('/api/gdpr/export', validateAzureADToken, async (req, res) => {
try {
const exportData = await gdprHandler.handleDataExportRequest(req.user.id);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Disposition', `attachment; filename="user-data-${req.user.id}.json"`);
res.json(exportData);
} catch (error) {
res.status(500).json({ error: 'Export failed' });
}
});
app.post('/api/gdpr/delete', validateAzureADToken, async (req, res) => {
try {
const result = await gdprHandler.handleDataDeletionRequest(req.user.id);
res.json(result);
} catch (error) {
res.status(500).json({ error: 'Deletion failed' });
}
});
Security monitoring and incident response
Threat detection
Copy
class ThreatDetector {
constructor() {
this.suspiciousActivityThresholds = {
maxRequestsPerMinute: 60,
maxFailedLogins: 5,
maxTokensPerHour: 100000
};
this.userActivityTracking = new Map();
}
async analyzeRequest(req, res, next) {
const userId = req.user?.id || req.ip;
const now = Date.now();
// Get or create user activity record
if (!this.userActivityTracking.has(userId)) {
this.userActivityTracking.set(userId, {
requests: [],
failedLogins: 0,
tokensUsed: 0,
lastReset: now
});
}
const activity = this.userActivityTracking.get(userId);
// Reset counters every hour
if (now - activity.lastReset > 60 * 60 * 1000) {
activity.requests = [];
activity.failedLogins = 0;
activity.tokensUsed = 0;
activity.lastReset = now;
}
// Track request
activity.requests.push(now);
// Remove requests older than 1 minute
activity.requests = activity.requests.filter(time => now - time < 60 * 1000);
// Check for suspicious activity
await this.checkForThreats(userId, activity, req);
next();
}
async checkForThreats(userId, activity, req) {
const threats = [];
// Check request rate
if (activity.requests.length > this.suspiciousActivityThresholds.maxRequestsPerMinute) {
threats.push({
type: 'rate_limit_exceeded',
severity: 'medium',
details: `${activity.requests.length} requests in the last minute`
});
}
// Check for suspicious patterns in requests
const suspiciousPatterns = [
/\bUNION\b.*\bSELECT\b/i, // SQL injection
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // XSS
/\.\.\/.*\.\.\//, // Path traversal
];
const content = req.body.message || '';
for (const pattern of suspiciousPatterns) {
if (pattern.test(content)) {
threats.push({
type: 'injection_attempt',
severity: 'high',
details: 'Suspicious patterns detected in request content'
});
break;
}
}
// Check for unusual model requests
if (req.body.model && !['gpt-4o', 'gpt-4o-mini', 'gpt-35-turbo'].includes(req.body.model)) {
threats.push({
type: 'invalid_model_request',
severity: 'low',
details: `Requested invalid model: ${req.body.model}`
});
}
// Log and respond to threats
for (const threat of threats) {
await this.handleThreat(userId, threat, req);
}
}
async handleThreat(userId, threat, req) {
auditLogger.logSecurityEvent(
threat.type,
threat.severity,
threat.details,
{
userId: userId,
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
endpoint: req.path,
method: req.method
}
);
// Take automated response based on severity
switch (threat.severity) {
case 'high':
await this.blockUser(userId, threat);
break;
case 'medium':
await this.throttleUser(userId, threat);
break;
case 'low':
// Just log for now
break;
}
// Send alert to security team
await this.sendSecurityAlert(threat, userId);
}
async blockUser(userId, threat) {
// Temporarily block user
const blockDuration = 15 * 60 * 1000; // 15 minutes
const blockUntil = Date.now() + blockDuration;
// Store block in cache/database
await this.storeUserBlock(userId, blockUntil, threat);
auditLogger.logSecurityEvent(
'user_blocked',
'high',
`User blocked due to ${threat.type}`,
{ userId: userId, blockUntil: new Date(blockUntil).toISOString() }
);
}
async throttleUser(userId, threat) {
// Implement rate limiting
const activity = this.userActivityTracking.get(userId);
if (activity) {
activity.throttled = true;
activity.throttleUntil = Date.now() + (5 * 60 * 1000); // 5 minutes
}
}
async sendSecurityAlert(threat, userId) {
// Send alert via multiple channels
const alert = {
type: 'security_threat',
severity: threat.severity,
threatType: threat.type,
userId: userId,
timestamp: new Date().toISOString(),
details: threat.details
};
// Send to Slack, email, etc.
await sendAlertToSlack(alert);
}
}
const threatDetector = new ThreatDetector();
app.use(threatDetector.analyzeRequest.bind(threatDetector));
Security testing
Automated security testing
Copy
// Security test suite
const request = require('supertest');
const app = require('../app');
describe('Security Tests', () => {
describe('Authentication', () => {
test('should reject requests without authentication', async () => {
const response = await request(app)
.post('/api/chat')
.send({ message: 'test' });
expect(response.status).toBe(401);
});
test('should reject invalid tokens', async () => {
const response = await request(app)
.post('/api/chat')
.set('Authorization', 'Bearer invalid-token')
.send({ message: 'test' });
expect(response.status).toBe(401);
});
});
describe('Input Validation', () => {
test('should reject SQL injection attempts', async () => {
const maliciousInput = "'; DROP TABLE users; --";
const response = await request(app)
.post('/api/chat')
.set('Authorization', 'Bearer valid-token')
.send({ message: maliciousInput });
expect(response.status).toBe(400);
expect(response.body.error).toContain('malicious content');
});
test('should reject XSS attempts', async () => {
const xssInput = '<script>alert("xss")</script>';
const response = await request(app)
.post('/api/chat')
.set('Authorization', 'Bearer valid-token')
.send({ message: xssInput });
expect(response.status).toBe(400);
});
test('should sanitize HTML content', async () => {
const htmlInput = '<b>bold text</b> and <i>italic</i>';
const response = await request(app)
.post('/api/chat')
.set('Authorization', 'Bearer valid-token')
.send({ message: htmlInput });
// Should process successfully but with sanitized content
expect(response.status).toBe(200);
// Verify HTML was stripped in logs
});
});
describe('Rate Limiting', () => {
test('should enforce rate limits', async () => {
const promises = [];
// Send many requests rapidly
for (let i = 0; i < 100; i++) {
promises.push(
request(app)
.post('/api/chat')
.set('Authorization', 'Bearer valid-token')
.send({ message: 'test message' })
);
}
const responses = await Promise.all(promises);
const tooManyRequests = responses.filter(r => r.status === 429);
expect(tooManyRequests.length).toBeGreaterThan(0);
});
});
describe('Data Protection', () => {
test('should detect and sanitize PII', async () => {
const piiMessage = 'My SSN is 123-45-6789 and email is test@example.com';
const response = await request(app)
.post('/api/chat')
.set('Authorization', 'Bearer valid-token')
.send({ message: piiMessage });
expect(response.status).toBe(200);
expect(response.body.warning).toBeDefined();
expect(response.body.warning.detected).toBe(true);
});
});
});
Next steps
Monitor Performance
Set up monitoring to detect security incidents
Deploy to Production
Deploy with security hardening in production
Security is an ongoing process, not a one-time setup. Regularly review and update your security measures, conduct security audits, and stay informed about new threats and best practices. Consider engaging security professionals for periodic assessments of your application.

