erp-core/docs/07-devops/SECURITY-HARDENING.md

956 lines
25 KiB
Markdown

# 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<string[]>('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<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async comparePassword(password: string, hash: string): Promise<boolean> {
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<boolean> {
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
<div>{userInput}</div> // Safe
// Dangerous: Bypass escaping
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} /> // 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<any> {
const result = await this.client.read(path);
return result.data;
}
async setSecret(path: string, data: any): Promise<void> {
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