Files
pos-system/docs/en/skills/security.md
Ho Ngoc Hai 2640b351c3 Enhance documentation with detailed diagrams and structured flows
- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes.
- Introduced configuration loading flow in configuration-management skill to clarify the configuration process.
- Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers.
- Enhanced various skills with additional diagrams to improve understanding of complex concepts.

These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
2026-01-01 23:22:54 +07:00

26 KiB

name, description
name description
security Security best practices and patterns for GoodGo microservices platform. Use when implementing authentication, authorization, data protection, input validation, rate limiting, secrets management, or security testing across all services.

Security Patterns for GoodGo Microservices

When to Use This Skill

Use this skill when:

  • Implementing authentication and authorization in any service
  • Protecting sensitive data (PII, credentials, tokens)
  • Validating user inputs and file uploads
  • Implementing rate limiting and DDoS protection
  • Setting up audit logging and security monitoring
  • Encrypting data at rest and in transit
  • Managing secrets and credentials
  • Implementing security testing
  • Handling security incidents
  • Designing secure API endpoints

Core Security Principles

  1. Defense in Depth: Multiple layers of security controls
  2. Least Privilege: Grant minimum required permissions
  3. Fail Secure: Default to deny access
  4. Separation of Duties: Critical operations require multiple approvals
  5. Audit Everything: Log all security-relevant events
  6. Encrypt Sensitive Data: PII, tokens, credentials must be encrypted
  7. Validate All Inputs: Never trust user input
  8. Principle of Least Exposure: Minimize attack surface
  9. Secure by Default: Security built-in, not bolted on
  10. Assume Breach: Design for detection and response

Authentication & Authorization

JWT Token Validation

The following diagram illustrates the authentication flow when a client makes a request with a JWT token:

sequenceDiagram
    participant Client
    participant Middleware as Auth Middleware
    participant JWTService as JWT Service
    participant Request as Express Request

    Client->>Middleware: HTTP Request with Token
    Middleware->>Middleware: Extract token from<br/>Authorization header or cookie
    
    alt Token not found
        Middleware->>Client: 401 Unauthorized<br/>(AUTH_REQUIRED)
    else Token found
        Middleware->>JWTService: verifyAccessToken(token)
        
        alt Token invalid or expired
            JWTService->>Middleware: Verification failed
            Middleware->>Client: 401 Unauthorized<br/>(INVALID_TOKEN)
        else Token valid
            JWTService->>Middleware: Payload (sub, email, roles, permissions)
            Middleware->>Request: Attach user to req.user
            Middleware->>Client: Continue to next middleware
        end
    end
// src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { jwtService } from '@goodgo/auth-sdk';
import { logger } from '@goodgo/logger';

export const authenticate = () => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      // Extract token from Authorization header or cookie
      let token: string | null = null;
      
      const authHeader = req.headers.authorization;
      if (authHeader?.startsWith('Bearer ')) {
        token = authHeader.substring(7);
      } else if (req.cookies?.access_token) {
        token = req.cookies.access_token;
      }

      if (!token) {
        return res.status(401).json({
          success: false,
          error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
        });
      }

      // Verify token
      const payload = await jwtService.verifyAccessToken(token);
      
      // Attach user to request
      req.user = {
        id: payload.sub,
        userId: payload.sub,
        email: payload.email,
        roles: payload.roles || [],
        permissions: payload.permissions || []
      };

      next();
    } catch (error) {
      logger.warn('Authentication failed', { error: error.message });
      return res.status(401).json({
        success: false,
        error: { code: 'INVALID_TOKEN', message: 'Invalid or expired token' }
      });
    }
  };
};

Role-Based Authorization

The authorization decision flow determines whether a user has the required permissions to access a resource:

flowchart TD
    Start([Request Received]) --> CheckAuth{User<br/>Authenticated?}
    
    CheckAuth -->|No| Return401[Return 401<br/>AUTH_REQUIRED]
    CheckAuth -->|Yes| CheckType{Authorization<br/>Type?}
    
    CheckType -->|Role-Based| CheckRole{User has<br/>Required Role?}
    CheckType -->|Permission-Based| CheckPermission{User has<br/>Resource:Action<br/>Permission?}
    CheckType -->|Ownership| CheckOwnership{Resource ID<br/>matches User ID?}
    
    CheckRole -->|No| LogDenial[Log Permission Denied<br/>with user roles]
    CheckPermission -->|No| LogDenial
    CheckOwnership -->|No| LogDenial
    
    LogDenial --> Return403[Return 403<br/>FORBIDDEN]
    
    CheckRole -->|Yes| Allow[Allow Request<br/>Continue to Handler]
    CheckPermission -->|Yes| Allow
    CheckOwnership -->|Yes| Allow
    
    Return401 --> End([End])
    Return403 --> End
    Allow --> End
    
    style CheckAuth fill:#e1f5ff
    style CheckType fill:#e1f5ff
    style Return401 fill:#ffebee
    style Return403 fill:#ffebee
    style Allow fill:#e8f5e9
// src/middlewares/rbac.middleware.ts
export const requireRole = (...allowedRoles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
      });
    }

    const userRoles = req.user.roles || [];
    const hasRole = userRoles.some(role => allowedRoles.includes(role));

    if (!hasRole) {
      logger.warn('Access denied - insufficient role', {
        userId: req.user.id,
        userRoles,
        requiredRoles: allowedRoles
      });
      
      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
      });
    }

    next();
  };
};

// Permission-based authorization
export const requirePermission = (resource: string, action: string) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
      });
    }

    const permission = `${resource}:${action}`;
    const hasPermission = req.user.permissions?.includes(permission);

    if (!hasPermission) {
      logger.warn('Access denied - insufficient permission', {
        userId: req.user.id,
        required: permission
      });
      
      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }
      });
    }

    next();
  };
};

// Usage in routes
router.post(
  '/api/v1/users',
  authenticate(),
  requirePermission('users', 'create'),
  userController.create
);

Resource Ownership Validation

// Ensure users can only access their own resources
export const requireOwnership = (resourceIdParam: string = 'id') => {
  return (req: Request, res: Response, next: NextFunction) => {
    const resourceId = req.params[resourceIdParam];
    const userId = req.user?.id;

    if (resourceId !== userId) {
      logger.warn('Access denied - resource ownership mismatch', {
        userId,
        resourceId
      });
      
      return res.status(403).json({
        success: false,
        error: { code: 'FORBIDDEN', message: 'Access denied' }
      });
    }

    next();
  };
};

Data Protection

Encryption Service

The encryption and decryption flow for protecting sensitive data at rest:

sequenceDiagram
    participant Service
    participant EncryptionService
    participant Crypto as Node.js Crypto
    participant Database

    Note over Service,Database: Encryption Flow
    Service->>EncryptionService: encrypt(plaintext)
    EncryptionService->>Crypto: Generate random IV<br/>(16 bytes)
    EncryptionService->>Crypto: Create cipher<br/>(AES-256-GCM)
    EncryptionService->>Crypto: Encrypt plaintext
    Crypto->>EncryptionService: Encrypted data + Auth Tag
    EncryptionService->>Service: Format: iv:tag:ciphertext
    Service->>Database: Store encrypted data

    Note over Service,Database: Decryption Flow
    Service->>Database: Retrieve encrypted data
    Database->>Service: iv:tag:ciphertext
    Service->>EncryptionService: decrypt(encryptedText)
    EncryptionService->>EncryptionService: Split iv, tag, ciphertext
    EncryptionService->>Crypto: Create decipher<br/>(AES-256-GCM)
    EncryptionService->>Crypto: Set auth tag
    EncryptionService->>Crypto: Decrypt ciphertext
    Crypto->>EncryptionService: Plaintext
    EncryptionService->>Service: Return plaintext
// src/core/security/encryption.service.ts
import crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;

export class EncryptionService {
  private getKey(): Buffer {
    const secret = process.env.ENCRYPTION_KEY;
    if (!secret || secret.length < 32) {
      throw new Error('ENCRYPTION_KEY must be at least 32 characters');
    }
    return crypto.scryptSync(secret, 'salt', 32);
  }

  encrypt(text: string): string {
    const key = this.getKey();
    const iv = crypto.randomBytes(IV_LENGTH);
    const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
    
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const tag = cipher.getAuthTag();
    
    return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted}`;
  }

  decrypt(encryptedText: string): string {
    const [ivHex, tagHex, encrypted] = encryptedText.split(':');
    const iv = Buffer.from(ivHex, 'hex');
    const tag = Buffer.from(tagHex, 'hex');
    
    const key = this.getKey();
    const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
    decipher.setAuthTag(tag);
    
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  }
}

// Usage: Encrypt PII before storing
const encryption = new EncryptionService();
const encryptedPhone = encryption.encrypt(user.phone);

Password Hashing

The password hashing and verification flow:

sequenceDiagram
    participant User
    participant Service
    participant PasswordService
    participant Bcrypt
    participant Database

    Note over User,Database: Registration/Password Change
    User->>Service: Submit password
    Service->>PasswordService: hash(password)
    PasswordService->>Bcrypt: bcrypt.hash(password, 12)
    Bcrypt->>Bcrypt: Generate salt
    Bcrypt->>Bcrypt: Hash with cost factor 12
    Bcrypt->>PasswordService: Hashed password
    PasswordService->>Service: Return hash
    Service->>Database: Store passwordHash
    Service->>Service: Sanitize password<br/>before logging

    Note over User,Database: Login Verification
    User->>Service: Submit credentials
    Service->>Database: Fetch user by email
    Database->>Service: User with passwordHash
    Service->>PasswordService: verify(password, hash)
    PasswordService->>Bcrypt: bcrypt.compare(password, hash)
    Bcrypt->>PasswordService: Boolean result
    PasswordService->>Service: Return verification result
    
    alt Password matches
        Service->>User: Authentication success
    else Password mismatch
        Service->>User: Invalid credentials<br/>(generic error)
    end
// Always use bcrypt with appropriate cost factor
import bcrypt from 'bcrypt';

const SALT_ROUNDS = 12; // Production: 12, Development: 10

export class PasswordService {
  async hash(password: string): Promise<string> {
    return bcrypt.hash(password, SALT_ROUNDS);
  }

  async verify(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }

  // Never log passwords
  sanitizeForLogging(data: any): any {
    const sanitized = { ...data };
    if (sanitized.password) sanitized.password = '[REDACTED]';
    if (sanitized.passwordHash) sanitized.passwordHash = '[REDACTED]';
    return sanitized;
  }
}

Token Hashing

// Hash tokens before storing in database
import crypto from 'crypto';

export class TokenService {
  hashToken(token: string): string {
    const salt = process.env.TOKEN_SALT || 'default-salt-change-in-production';
    return crypto
      .createHash('sha256')
      .update(token + salt)
      .digest('hex');
  }

  generateSecureToken(length: number = 32): string {
    return crypto.randomBytes(length).toString('hex');
  }
}

Input Validation

Zod Schema Validation

// Always validate inputs with Zod
import { z } from 'zod';

// DTO with validation
export const CreateUserDto = z.object({
  email: z.string().email('Invalid email format'),
  password: z.string()
    .min(8, 'Password must be at least 8 characters')
    .regex(/[A-Z]/, 'Password must contain uppercase letter')
    .regex(/[a-z]/, 'Password must contain lowercase letter')
    .regex(/[0-9]/, 'Password must contain number')
    .regex(/[^A-Za-z0-9]/, 'Password must contain special character'),
  phone: z.string()
    .regex(/^\+[1-9]\d{1,14}$/, 'Invalid phone format (E.164)')
    .optional(),
  name: z.string().min(1).max(255)
});

// In controller
export class UserController {
  async create(req: Request, res: Response) {
    try {
      const dto = CreateUserDto.parse(req.body);
      const user = await this.service.create(dto);
      res.status(201).json({ success: true, data: user });
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({
          success: false,
          error: {
            code: 'VALIDATION_ERROR',
            message: 'Invalid input data',
            details: error.errors
          }
        });
      }
      throw error;
    }
  }
}

File Upload Validation

// Validate file uploads
import fileType from 'file-type';

export class FileValidationService {
  private readonly MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
  private readonly ALLOWED_TYPES = ['image/jpeg', 'image/png', 'application/pdf'];

  async validateFile(file: Express.Multer.File): Promise<void> {
    // Size check
    if (file.size > this.MAX_FILE_SIZE) {
      throw new HttpError(400, 'FILE_TOO_LARGE', 'File exceeds maximum size');
    }

    // Type check
    if (!this.ALLOWED_TYPES.includes(file.mimetype)) {
      throw new HttpError(400, 'INVALID_FILE_TYPE', 'File type not allowed');
    }

    // Content validation (prevent MIME type spoofing)
    const type = await fileType.fromBuffer(file.buffer);
    if (!type || !this.ALLOWED_TYPES.includes(type.mime)) {
      throw new HttpError(400, 'INVALID_FILE_CONTENT', 'File content mismatch');
    }

    // TODO: Add virus scanning for production
  }
}

SQL Injection Prevention

// Always use Prisma parameterized queries (automatic)
// Never use string concatenation for queries

// ❌ BAD - Never do this
const query = `SELECT * FROM users WHERE email = '${email}'`;

// ✅ GOOD - Use Prisma
const user = await prisma.user.findUnique({
  where: { email }
});

// ✅ GOOD - For dynamic queries
const where: any = {};
if (email) where.email = email;
if (status) where.status = status;

const users = await prisma.user.findMany({ where });

Rate Limiting

// Implement rate limiting for all endpoints
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL);

// Standard rate limit
export const standardLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:standard:'
  }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  message: 'Too many requests, please try again later',
  standardHeaders: true,
  legacyHeaders: false
});

// Strict rate limit for sensitive operations
export const strictLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:strict:'
  }),
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 10,
  message: 'Rate limit exceeded for this operation'
});

// Login-specific rate limit
export const loginLimiter = rateLimit({
  store: new RedisStore({
    client: redis,
    prefix: 'rl:login:'
  }),
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 login attempts per 15 minutes
  skipSuccessfulRequests: true,
  message: 'Too many login attempts, please try again later'
});

// Usage
router.post('/api/v1/auth/login', loginLimiter, authController.login);
router.post('/api/v1/users', authenticate(), strictLimiter, userController.create);

Error Handling Security

// Sanitize error messages to prevent information disclosure
export class SecureErrorHandler {
  handleError(error: Error, req: Request, res: Response) {
    const isDev = process.env.NODE_ENV === 'development';
    const isProd = process.env.NODE_ENV === 'production';

    // Log full error internally
    logger.error('Request error', {
      error: error.message,
      stack: error.stack,
      path: req.path,
      method: req.method,
      userId: req.user?.id
    });

    // Don't expose user existence
    if (error.message.includes('user not found') || 
        error.message.includes('invalid credentials')) {
      return res.status(401).json({
        success: false,
        error: {
          code: 'INVALID_CREDENTIALS',
          message: 'Invalid email or password'
        }
      });
    }

    // Validation errors - safe to expose
    if (error instanceof z.ZodError) {
      return res.status(400).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Invalid input data',
          details: error.errors
        }
      });
    }

    // Generic errors for production
    if (isProd) {
      return res.status(500).json({
        success: false,
        error: {
          code: 'INTERNAL_ERROR',
          message: 'An error occurred. Please try again later.'
        }
      });
    }

    // Detailed errors only in development
    return res.status(500).json({
      success: false,
      error: {
        code: 'INTERNAL_ERROR',
        message: error.message,
        stack: isDev ? error.stack : undefined
      }
    });
  }
}

Secrets Management

// Never hardcode secrets
// Always use environment variables with validation
import { z } from 'zod';

const secretsSchema = z.object({
  JWT_SECRET: z.string().min(32, 'JWT_SECRET must be at least 32 characters'),
  JWT_REFRESH_SECRET: z.string().min(32),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url().optional(),
  ENCRYPTION_KEY: z.string().min(32).optional()
});

export const secrets = secretsSchema.parse(process.env);

// For production, use secret management:
// - AWS Secrets Manager
// - HashiCorp Vault
// - Kubernetes Secrets
// - Azure Key Vault

// Rotate secrets regularly (quarterly recommended)

Audit Logging

// Log all security-relevant events
export class AuditService {
  async logSecurityEvent(
    event: string,
    userId: string | null,
    details: Record<string, any>,
    req?: Request
  ) {
    await this.prisma.auditLog.create({
      data: {
        event,
        userId,
        type: 'SECURITY',
        details: this.sanitizeDetails(details),
        ipAddress: req?.ip || details.ipAddress,
        userAgent: req?.get('user-agent'),
        timestamp: new Date()
      }
    });
  }

  // Sanitize PII from logs
  private sanitizeDetails(details: Record<string, any>): Record<string, any> {
    const sensitive = ['password', 'token', 'secret', 'ssn', 'creditCard'];
    const sanitized = { ...details };
    
    for (const key of sensitive) {
      if (sanitized[key]) {
        sanitized[key] = '[REDACTED]';
      }
    }
    
    return sanitized;
  }
}

// Usage
await auditService.logSecurityEvent('LOGIN_SUCCESS', user.id, {
  email: user.email,
  ipAddress: req.ip
}, req);

await auditService.logSecurityEvent('PERMISSION_DENIED', user.id, {
  resource: 'users',
  action: 'delete',
  targetId: targetUserId
}, req);

Security Headers

// Add security headers middleware
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

// Additional headers
app.use((req, res, next) => {
  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');
  next();
});

CORS Configuration

// Configure CORS securely
import cors from 'cors';

const allowedOrigins = process.env.CORS_ORIGIN?.split(',') || [];

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Request-ID'],
  maxAge: 86400 // 24 hours
}));

Security Testing

// Security test patterns
describe('Security Tests', () => {
  it('should prevent SQL injection', async () => {
    const maliciousInput = "'; DROP TABLE users; --";
    const response = await request(app)
      .get(`/api/v1/users?search=${encodeURIComponent(maliciousInput)}`)
      .set('Authorization', `Bearer ${token}`);
    
    expect(response.status).not.toBe(500);
    // Should return 400 or empty results, not crash
  });

  it('should prevent XSS attacks', async () => {
    const xssPayload = '<script>alert("XSS")</script>';
    const response = await request(app)
      .post('/api/v1/users')
      .send({ email: xssPayload, password: 'test123' });
    
    // Response should sanitize or reject
    expect(response.body.data?.email).not.toContain('<script>');
  });

  it('should enforce authentication', async () => {
    const response = await request(app)
      .get('/api/v1/users');
    
    expect(response.status).toBe(401);
  });

  it('should enforce authorization', async () => {
    const userToken = await createUserToken({ roles: ['user'] });
    const response = await request(app)
      .delete('/api/v1/users/123')
      .set('Authorization', `Bearer ${userToken}`);
    
    expect(response.status).toBe(403);
  });

  it('should rate limit excessive requests', async () => {
    const requests = Array(20).fill(null).map(() =>
      request(app).get('/api/v1/users')
    );
    
    const responses = await Promise.all(requests);
    const rateLimited = responses.filter(r => r.status === 429);
    
    expect(rateLimited.length).toBeGreaterThan(0);
  });
});

Security Checklist

Before deploying any service:

  • All endpoints require authentication (except public)
  • Authorization checks implemented (RBAC/ABAC)
  • Input validation with Zod schemas
  • Rate limiting configured
  • Error messages sanitized (no info disclosure)
  • PII encrypted at rest
  • Passwords hashed with bcrypt (cost 12+)
  • Tokens hashed before storing
  • Secrets in environment variables (never hardcoded)
  • HTTPS enforced (TLS 1.2+)
  • CORS configured correctly
  • Security headers set (helmet)
  • Audit logging enabled
  • SQL injection prevented (use Prisma)
  • XSS prevention (input sanitization)
  • File upload validation
  • Security tests passing
  • Dependencies scanned for vulnerabilities
  • Secrets rotation plan in place

Common Security Anti-Patterns

// ❌ BAD: Hardcoded secrets
const SECRET = 'my-secret-key';

// ✅ GOOD: Environment variables
const SECRET = process.env.JWT_SECRET;

// ❌ BAD: Plain text passwords
await prisma.user.create({ data: { password: password } });

// ✅ GOOD: Hashed passwords
await prisma.user.create({ 
  data: { passwordHash: await bcrypt.hash(password, 12) } 
});

// ❌ BAD: Exposing user existence
if (!user) {
  throw new Error('User not found'); // Reveals user doesn't exist
}

// ✅ GOOD: Generic error
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
  throw new Error('Invalid credentials');
}

// ❌ BAD: No input validation
const email = req.body.email;

// ✅ GOOD: Validate with Zod
const { email } = CreateUserDto.parse(req.body);

// ❌ BAD: Stack traces in production
res.status(500).json({ error: error.stack });

// ✅ GOOD: Sanitized errors
res.status(500).json({ 
  error: { code: 'INTERNAL_ERROR', message: 'An error occurred' } 
});

Incident Response

// Security incident detection and response
export class SecurityIncidentService {
  async detectAnomaly(userId: string, event: string, context: any) {
    // Check for suspicious patterns
    const recentEvents = await this.getRecentEvents(userId, '1h');
    
    if (recentEvents.length > 10) {
      await this.triggerAlert('SUSPICIOUS_ACTIVITY', {
        userId,
        eventCount: recentEvents.length,
        timeWindow: '1h'
      });
    }

    // Check for privilege escalation attempts
    if (event === 'PERMISSION_DENIED' && context.requiredPermission) {
      await this.logSecurityEvent('PRIVILEGE_ESCALATION_ATTEMPT', userId, context);
    }
  }

  async triggerAlert(type: string, details: any) {
    // Send to monitoring system
    logger.error('Security alert', { type, details });
    
    // TODO: Integrate with PagerDuty, Slack, etc.
  }
}

Resources