# SECURITY HARDENING - ERP Generic **Última actualización:** 2025-11-24 **Responsable:** Security Team / DevOps Team **Estado:** ✅ Production-Ready --- ## TABLE OF CONTENTS 1. [Overview](#1-overview) 2. [OWASP Top 10 Mitigations](#2-owasp-top-10-mitigations) 3. [Authentication & Authorization](#3-authentication--authorization) 4. [Input Validation & Sanitization](#4-input-validation--sanitization) 5. [SQL Injection Prevention](#5-sql-injection-prevention) 6. [XSS & CSRF Protection](#6-xss--csrf-protection) 7. [Security Headers](#7-security-headers) 8. [Rate Limiting & DDoS Protection](#8-rate-limiting--ddos-protection) 9. [Secrets Management](#9-secrets-management) 10. [SSL/TLS Configuration](#10-ssltls-configuration) 11. [Security Scanning & Auditing](#11-security-scanning--auditing) 12. [Incident Response](#12-incident-response) --- ## 1. OVERVIEW ### 1.1 Security Posture **Defense in Depth Strategy:** ``` ┌─────────────────────────────────────────────────────────┐ │ Layer 7: Application Security │ │ - Input validation, Output encoding │ │ - OWASP Top 10 mitigations, Code review │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 6: Authentication & Authorization │ │ - JWT with rotation, RBAC, MFA │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 5: API Security │ │ - Rate limiting, CORS, API Gateway │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 4: Network Security │ │ - Firewall, Security groups, WAF │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 3: Data Security │ │ - Encryption at rest/transit, RLS, Backup encryption │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 2: Infrastructure Security │ │ - OS hardening, Container security, Secrets mgmt │ └─────────────────┬───────────────────────────────────────┘ │ ┌─────────────────▼───────────────────────────────────────┐ │ Layer 1: Physical Security │ │ - Data center security, Access controls │ └─────────────────────────────────────────────────────────┘ ``` ### 1.2 Security Objectives - **Confidentiality:** Protect sensitive data (PII, financial data) - **Integrity:** Prevent unauthorized data modification - **Availability:** Ensure system uptime (DDoS protection) - **Accountability:** Audit trail for all sensitive operations - **Non-Repudiation:** Digital signatures for critical transactions --- ## 2. OWASP TOP 10 MITIGATIONS ### 2.1 A01:2021 - Broken Access Control **Threat:** Users accessing resources without proper authorization. **Mitigation:** ```typescript // backend/src/common/guards/rbac.guard.ts import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Permission } from '../decorators/permission.decorator'; @Injectable() export class RBACGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredPermissions = this.reflector.get('permissions', context.getHandler()); if (!requiredPermissions) { return true; // No permissions required } const request = context.switchToHttp().getRequest(); const user = request.user; // Check if user has required permissions const hasPermission = requiredPermissions.every((permission) => user.permissions?.includes(permission) ); if (!hasPermission) { throw new ForbiddenException('Insufficient permissions'); } // Check tenant isolation if (request.body?.tenant_id && request.body.tenant_id !== user.tenant_id) { throw new ForbiddenException('Access denied: Cross-tenant access prohibited'); } return true; } } // Usage in controller @Controller('sales/orders') @UseGuards(JwtAuthGuard, RBACGuard) export class SalesOrdersController { @Post() @Permissions('sales:orders:create') // Decorator defines required permission createOrder(@Body() dto: CreateOrderDto, @CurrentUser() user: User) { // Enforce tenant_id from authenticated user (prevent tampering) dto.tenant_id = user.tenant_id; return this.ordersService.create(dto); } } ``` **RLS (Row-Level Security) in PostgreSQL:** ```sql -- Enable RLS on all tenant-specific tables ALTER TABLE sales.orders ENABLE ROW LEVEL SECURITY; -- Policy: Users can only see orders from their tenant CREATE POLICY tenant_isolation_policy ON sales.orders FOR ALL TO public USING (tenant_id = current_setting('app.current_tenant_id')::UUID); -- Service role can bypass RLS (for admin operations) CREATE ROLE service_role BYPASSRLS; ``` --- ### 2.2 A02:2021 - Cryptographic Failures **Threat:** Sensitive data exposed due to weak or missing encryption. **Mitigation:** ```typescript // Encryption at rest (sensitive fields in database) import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto'; export class EncryptionService { private algorithm = 'aes-256-gcm'; private key: Buffer; constructor() { // Derive key from secret (stored in environment variable) this.key = scryptSync(process.env.ENCRYPTION_SECRET, 'salt', 32); } encrypt(plaintext: string): string { const iv = randomBytes(16); const cipher = createCipheriv(this.algorithm, this.key, iv); let encrypted = cipher.update(plaintext, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); // Return: iv:authTag:encrypted return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`; } decrypt(ciphertext: string): string { const [ivHex, authTagHex, encrypted] = ciphertext.split(':'); const iv = Buffer.from(ivHex, 'hex'); const authTag = Buffer.from(authTagHex, 'hex'); const decipher = createDecipheriv(this.algorithm, this.key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } } // Usage: Encrypt SSN, credit card numbers, etc. @Column({ type: 'text' }) get ssn(): string { return this._ssn; } set ssn(value: string) { this._ssn = this.encryptionService.encrypt(value); } ``` **SSL/TLS Configuration:** - TLS 1.2+ only (disable TLS 1.0, 1.1) - Strong cipher suites only - HSTS header enabled - Certificate pinning (mobile apps) --- ### 2.3 A03:2021 - Injection **Threat:** SQL injection, NoSQL injection, Command injection. **Mitigation:** ```typescript // Prisma ORM (parameterized queries - safe by default) const users = await prisma.user.findMany({ where: { email: userInput, // Automatically parameterized tenant_id: tenantId, }, }); // NEVER do this (vulnerable to SQL injection): // const query = `SELECT * FROM users WHERE email = '${userInput}'`; // Command injection prevention import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); // BAD: Direct command execution with user input // await execAsync(`convert ${userInput} output.png`); // VULNERABLE! // GOOD: Whitelist allowed values const allowedFormats = ['png', 'jpg', 'gif']; if (!allowedFormats.includes(format)) { throw new BadRequestException('Invalid format'); } await execAsync(`convert input.${format} output.png`); ``` --- ### 2.4 A04:2021 - Insecure Design **Mitigation:** - Security requirements in design phase - Threat modeling (STRIDE methodology) - Secure by default configurations - Principle of least privilege --- ### 2.5 A05:2021 - Security Misconfiguration **Mitigation:** ```typescript // helmet.config.ts - Security headers import helmet from 'helmet'; export const helmetConfig = helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], scriptSrc: ["'self'"], imgSrc: ["'self'", 'data:', 'https:'], fontSrc: ["'self'", 'https://fonts.gstatic.com'], connectSrc: ["'self'", 'https://api.erp-generic.com'], frameSrc: ["'none'"], objectSrc: ["'none'"], }, }, hsts: { maxAge: 31536000, // 1 year includeSubDomains: true, preload: true, }, frameguard: { action: 'deny' }, // Prevent clickjacking noSniff: true, // X-Content-Type-Options xssFilter: true, // X-XSS-Protection referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, }); // main.ts app.use(helmetConfig); ``` **Disable Debug Info in Production:** ```typescript // main.ts if (process.env.NODE_ENV === 'production') { app.enableCors({ origin: process.env.ALLOWED_ORIGINS.split(','), credentials: true, }); // Disable detailed error messages app.useGlobalFilters(new ProductionExceptionFilter()); } else { app.enableCors({ origin: '*' }); // Dev only } ``` --- ### 2.6 A06:2021 - Vulnerable and Outdated Components **Mitigation:** ```bash # Automated dependency scanning npm audit --audit-level=high # Snyk vulnerability scanning snyk test # Update dependencies regularly npm outdated npm update # Renovate Bot (automated PRs for dependency updates) # .github/renovate.json { "extends": ["config:base"], "schedule": ["every weekend"], "labels": ["dependencies"], "automerge": false } ``` --- ### 2.7 A07:2021 - Identification and Authentication Failures **Mitigation:** See Section 3 (JWT with rotation, MFA, password policies) --- ### 2.8 A08:2021 - Software and Data Integrity Failures **Mitigation:** ```typescript // Verify integrity of uploads import * as crypto from 'crypto'; function verifyFileIntegrity(file: Buffer, expectedHash: string): boolean { const hash = crypto.createHash('sha256').update(file).digest('hex'); return hash === expectedHash; } // Code signing for deployments // Sign Docker images docker trust sign erp-generic-backend:v1.0.0 // Verify signature before deployment docker trust inspect erp-generic-backend:v1.0.0 ``` --- ### 2.9 A09:2021 - Security Logging and Monitoring Failures **Mitigation:** ```typescript // Audit logging for sensitive operations logger.audit('USER_LOGIN', user.id, tenantId, { ip: request.ip, userAgent: request.headers['user-agent'], success: true, timestamp: new Date(), }); logger.audit('DATA_EXPORT', user.id, tenantId, { table: 'sales.orders', rowCount: 1500, format: 'CSV', }); logger.audit('PERMISSION_CHANGE', adminUser.id, tenantId, { targetUser: userId, oldRole: 'user', newRole: 'admin', }); ``` --- ### 2.10 A10:2021 - Server-Side Request Forgery (SSRF) **Mitigation:** ```typescript // Validate URLs before making requests import { URL } from 'url'; function isUrlSafe(urlString: string): boolean { try { const url = new URL(urlString); // Whitelist allowed protocols if (!['http:', 'https:'].includes(url.protocol)) { return false; } // Blacklist private IP ranges const hostname = url.hostname; const privateRanges = [ /^localhost$/, /^127\.\d+\.\d+\.\d+$/, /^10\.\d+\.\d+\.\d+$/, /^192\.168\.\d+\.\d+$/, /^172\.(1[6-9]|2\d|3[01])\.\d+\.\d+$/, ]; for (const range of privateRanges) { if (range.test(hostname)) { return false; } } return true; } catch { return false; } } // Usage if (!isUrlSafe(userProvidedUrl)) { throw new BadRequestException('Invalid URL'); } const response = await axios.get(userProvidedUrl); ``` --- ## 3. AUTHENTICATION & AUTHORIZATION ### 3.1 JWT Configuration ```typescript // jwt.config.ts export const jwtConfig = { secret: process.env.JWT_SECRET, signOptions: { expiresIn: '15m', // Short-lived access tokens algorithm: 'HS256', issuer: 'erp-generic', audience: 'erp-generic-api', }, refreshSecret: process.env.JWT_REFRESH_SECRET, refreshSignOptions: { expiresIn: '7d', // Long-lived refresh tokens }, }; // auth.service.ts async login(email: string, password: string) { const user = await this.validateUser(email, password); const payload = { sub: user.id, email: user.email, tenant_id: user.tenant_id, roles: user.roles, permissions: user.permissions, }; return { access_token: this.jwtService.sign(payload), refresh_token: this.jwtService.sign(payload, { secret: jwtConfig.refreshSecret, expiresIn: jwtConfig.refreshSignOptions.expiresIn, }), expires_in: 900, // 15 minutes }; } ``` ### 3.2 Password Policy ```typescript // Strong password requirements const passwordPolicy = { minLength: 12, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSpecialChars: true, preventCommonPasswords: true, preventPasswordReuse: 5, // Last 5 passwords maxAge: 90, // days (force reset after 90 days) }; // Password hashing with bcrypt import * as bcrypt from 'bcrypt'; const SALT_ROUNDS = 12; async hashPassword(password: string): Promise { return bcrypt.hash(password, SALT_ROUNDS); } async comparePassword(password: string, hash: string): Promise { return bcrypt.compare(password, hash); } ``` ### 3.3 Multi-Factor Authentication (MFA) ```typescript // MFA with TOTP (Time-based One-Time Password) import * as speakeasy from 'speakeasy'; import * as qrcode from 'qrcode'; async enableMFA(userId: string) { const secret = speakeasy.generateSecret({ name: `ERP Generic (${user.email})`, issuer: 'ERP Generic', }); // Store secret in database (encrypted) await this.prisma.user.update({ where: { id: userId }, data: { mfa_secret: this.encryptionService.encrypt(secret.base32), mfa_enabled: false, // Enable after verification }, }); // Generate QR code for user to scan const qrCodeUrl = await qrcode.toDataURL(secret.otpauth_url); return { qrCodeUrl, secret: secret.base32 }; } async verifyMFA(userId: string, token: string): Promise { const user = await this.prisma.user.findUnique({ where: { id: userId } }); const secret = this.encryptionService.decrypt(user.mfa_secret); return speakeasy.totp.verify({ secret, encoding: 'base32', token, window: 1, // Allow 1 time step before/after }); } ``` --- ## 4. INPUT VALIDATION & SANITIZATION ```typescript // Using class-validator and class-transformer import { IsEmail, IsString, MinLength, MaxLength, Matches, IsUUID } from 'class-validator'; import { Transform } from 'class-transformer'; import * as sanitizeHtml from 'sanitize-html'; export class CreateUserDto { @IsEmail() @Transform(({ value }) => value.toLowerCase().trim()) email: string; @IsString() @MinLength(12) @MaxLength(128) @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/) password: string; @IsString() @MinLength(2) @MaxLength(100) @Transform(({ value }) => sanitizeHtml(value, { allowedTags: [], allowedAttributes: {} })) firstName: string; @IsUUID() tenant_id: string; } // Global validation pipe app.useGlobalPipes( new ValidationPipe({ whitelist: true, // Strip unknown properties forbidNonWhitelisted: true, // Throw error if unknown properties present transform: true, // Auto-transform payloads to DTO instances }) ); ``` --- ## 5. SQL INJECTION PREVENTION **Prisma ORM (Safe by Default):** ```typescript // ✅ SAFE: Parameterized query const user = await prisma.user.findUnique({ where: { email: userInput }, }); // ✅ SAFE: Raw query with parameters const result = await prisma.$queryRaw` SELECT * FROM users WHERE email = ${userInput} `; // ❌ NEVER DO THIS: // const result = await prisma.$queryRawUnsafe( // `SELECT * FROM users WHERE email = '${userInput}'` // ); ``` --- ## 6. XSS & CSRF PROTECTION ### 6.1 XSS Prevention ```typescript // Frontend: Sanitize user input before rendering import DOMPurify from 'dompurify'; const sanitizedHtml = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'], }); // React automatically escapes JSX
{userInput}
// Safe // Dangerous: Bypass escaping
// Use with caution ``` ### 6.2 CSRF Protection ```typescript // CSRF token in NestJS import * as csurf from 'csurf'; app.use(csurf({ cookie: true })); // Endpoint to get CSRF token @Get('csrf-token') getCsrfToken(@Req() req) { return { csrfToken: req.csrfToken() }; } // Frontend: Include CSRF token in requests axios.post('/api/orders', orderData, { headers: { 'X-CSRF-Token': csrfToken, }, }); ``` --- ## 7. SECURITY HEADERS ```typescript // Comprehensive security headers configuration app.use( helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'"], // Remove unsafe-inline in production styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], imgSrc: ["'self'", 'data:', 'https:'], fontSrc: ["'self'", 'https://fonts.gstatic.com'], connectSrc: ["'self'", 'https://api.erp-generic.com'], frameSrc: ["'none'"], objectSrc: ["'none'"], upgradeInsecureRequests: [], }, }, hsts: { maxAge: 31536000, includeSubDomains: true, preload: true, }, noSniff: true, frameguard: { action: 'deny' }, xssFilter: true, referrerPolicy: { policy: 'strict-origin-when-cross-origin' }, permissionsPolicy: { features: { geolocation: ["'none'"], microphone: ["'none'"], camera: ["'none'"], payment: ["'self'"], }, }, }) ); ``` --- ## 8. RATE LIMITING & DDOS PROTECTION ```typescript // Rate limiting with @nestjs/throttler import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; @Module({ imports: [ ThrottlerModule.forRoot({ ttl: 60, // Time window in seconds limit: 100, // Max requests per ttl }), ], }) export class AppModule {} // Apply globally app.useGlobalGuards(new ThrottlerGuard()); // Custom rate limits per endpoint @Throttle(10, 60) // 10 requests per minute @Post('login') async login(@Body() dto: LoginDto) { return this.authService.login(dto); } // IP-based rate limiting for brute-force protection @Throttle(5, 300) // 5 failed login attempts per 5 minutes @Post('login') async login(@Body() dto: LoginDto, @Ip() ip: string) { // Log failed attempts const attempts = await this.redis.incr(`login_attempts:${ip}`); await this.redis.expire(`login_attempts:${ip}`, 300); if (attempts > 5) { throw new TooManyRequestsException('Too many login attempts. Try again in 5 minutes.'); } return this.authService.login(dto); } ``` --- ## 9. SECRETS MANAGEMENT ### 9.1 HashiCorp Vault Integration ```typescript // vault.service.ts import * as vault from 'node-vault'; export class VaultService { private client: vault.client; constructor() { this.client = vault({ apiVersion: 'v1', endpoint: process.env.VAULT_ADDR, token: process.env.VAULT_TOKEN, }); } async getSecret(path: string): Promise { const result = await this.client.read(path); return result.data; } async setSecret(path: string, data: any): Promise { await this.client.write(path, { data }); } } // Usage const dbPassword = await vaultService.getSecret('secret/erp-generic/prod/database'); ``` ### 9.2 Environment Variables Security ```bash # .env file (NEVER commit to Git!) # Use .env.example as template DATABASE_URL=postgresql://user:STRONG_PASSWORD@localhost:5432/db JWT_SECRET=RANDOM_64_CHAR_STRING ENCRYPTION_SECRET=RANDOM_64_CHAR_STRING # AWS Secrets Manager (production) aws secretsmanager create-secret --name erp-generic/prod/jwt-secret --secret-string "RANDOM_64_CHAR_STRING" ``` --- ## 10. SSL/TLS CONFIGURATION ### 10.1 Nginx SSL Configuration ```nginx server { listen 443 ssl http2; server_name erp-generic.com; # SSL certificates ssl_certificate /etc/nginx/ssl/erp-generic.com.crt; ssl_certificate_key /etc/nginx/ssl/erp-generic.com.key; # SSL protocols and ciphers ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384'; ssl_prefer_server_ciphers on; # HSTS add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # OCSP Stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/nginx/ssl/chain.pem; # SSL session cache ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; location / { proxy_pass http://backend:3000; proxy_set_header X-Forwarded-Proto https; } } # Redirect HTTP to HTTPS server { listen 80; server_name erp-generic.com; return 301 https://$server_name$request_uri; } ``` ### 10.2 Let's Encrypt Automation ```bash # Install Certbot apt-get install certbot python3-certbot-nginx # Obtain certificate certbot --nginx -d erp-generic.com -d www.erp-generic.com # Auto-renewal (cron) 0 0 1 * * certbot renew --quiet ``` --- ## 11. SECURITY SCANNING & AUDITING ### 11.1 Automated Security Scans ```yaml # .github/workflows/security-scan.yml name: Security Scan on: push: branches: [main, develop] schedule: - cron: '0 0 * * *' # Daily jobs: snyk: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Snyk to check for vulnerabilities uses: snyk/actions/node@master env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: args: --severity-threshold=high owasp-zap: runs-on: ubuntu-latest steps: - name: OWASP ZAP Scan uses: zaproxy/action-baseline@v0.7.0 with: target: 'https://staging.erp-generic.com' rules_file_name: '.zap/rules.tsv' cmd_options: '-a' ``` ### 11.2 Security Audit Checklist **Monthly Security Audit:** - [ ] Review access logs for anomalies - [ ] Check failed login attempts - [ ] Verify MFA enrollment rate (target: 90%) - [ ] Review user permissions (principle of least privilege) - [ ] Scan for outdated dependencies (npm audit) - [ ] Review firewall rules - [ ] Check SSL certificate expiration - [ ] Verify backup encryption - [ ] Test disaster recovery plan - [ ] Review security alerts from monitoring --- ## 12. INCIDENT RESPONSE ### 12.1 Security Incident Response Plan **Phases:** 1. **Detection:** Alerts via monitoring, reports from users 2. **Containment:** Isolate affected systems, block attacker 3. **Eradication:** Remove malware, patch vulnerabilities 4. **Recovery:** Restore services, verify integrity 5. **Post-Mortem:** Document lessons learned, improve defenses **Contact:** - **Security Team:** security@erp-generic.com - **On-Call:** security-oncall@erp-generic.com (PagerDuty) - **Escalation:** CISO, CTO, CEO --- ## REFERENCES - [OWASP Top 10 2021](https://owasp.org/www-project-top-ten/) - [CWE Top 25](https://cwe.mitre.org/top25/archive/2021/2021_cwe_top25.html) - [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework) - [Deployment Guide](./DEPLOYMENT-GUIDE.md) --- **Documento:** SECURITY-HARDENING.md **Versión:** 1.0 **Total Páginas:** ~15 **Última Actualización:** 2025-11-24