Protección de Datos
La protección de datos es crítica, especialmente al manejar información financiera y personal (PII) en los módulos de Remuneraciones y Contabilidad.
Clasificación de Datos
Section titled “Clasificación de Datos”| Nivel | Descripción | Ejemplos | Controles |
|---|---|---|---|
| 🟢 Público | Información no sensible | Docs técnicos, Landing page | Ninguno específico |
| 🟡 Interno | Uso exclusivo de la org | IDs de usuario, config UI | Autenticación requerida |
| 🟠 Confidencial | Información sensible del negocio | Detalles de contratos, montos | RBAC, Auditoría de acceso |
| 🔴 Crítico | PII y secretos | RUT, Sueldos, Claves, Tokens | Cifrado, Acceso restringido, Logs enmascarados |
Manejo de Secretos
Section titled “Manejo de Secretos”- Nunca commitear credenciales en Git (
.envestá en.gitignore) - En desarrollo local, usar
.env.localno compartido - En producción (Render/Cloudflare), inyectar secretos como Variables de Entorno
- Rotar llaves críticas (DB Password, JWT Secret) cada 90 días
Enmascaramiento en Logs
Section titled “Enmascaramiento en Logs”Para prevenir fugas de datos en sistemas de monitoreo, sanitiza objetos antes de loguear:
const SENSITIVE_KEYS = ['password', 'token', 'secret', 'rut', 'sueldo', 'clave'];
function sanitizeLog(data: Record<string, any>): Record<string, any> { const sanitized: Record<string, any> = {};
for (const [key, value] of Object.entries(data)) { if (SENSITIVE_KEYS.some(k => key.toLowerCase().includes(k))) { sanitized[key] = '[REDACTED]'; } else if (typeof value === 'object' && value !== null) { sanitized[key] = sanitizeLog(value); // Recursivo } else { sanitized[key] = value; } }
return sanitized;}
// Usoconsole.log(sanitizeLog({ user: 'chris', password: 'secret123', rut: '12345678-9' }));// Output: { user: 'chris', password: '[REDACTED]', rut: '[REDACTED]' }Estrategias de Cifrado
Section titled “Estrategias de Cifrado”Implementamos cifrado en múltiples capas:
Todo tráfico HTTP es exclusivamente HTTPS (TLS 1.2+).
| Control | Configuración |
|---|---|
| HSTS | max-age=31536000 (1 año) |
| Certificados | Gestionados por Cloudflare + Render |
| TLS Version | Mínimo 1.2, preferido 1.3 |
Strict-Transport-Security: max-age=31536000; includeSubDomainsX-Content-Type-Options: nosniffX-Frame-Options: DENYPostgreSQL (Neon) utiliza cifrado de disco transparente. Para datos altamente sensibles, aplicamos cifrado adicional:
import { webcrypto } from 'node:crypto';
const ALGORITHM = 'AES-GCM';const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex'); // 32 bytes
export async function encrypt(text: string): Promise<string> { const iv = webcrypto.getRandomValues(new Uint8Array(12)); const encoded = new TextEncoder().encode(text);
const key = await webcrypto.subtle.importKey( 'raw', KEY, ALGORITHM, false, ['encrypt'] );
const encrypted = await webcrypto.subtle.encrypt( { name: ALGORITHM, iv }, key, encoded );
// Format: IV:EncryptedData return `${Buffer.from(iv).toString('hex')}:${Buffer.from(encrypted).toString('hex')}`;}
export async function decrypt(ciphertext: string): Promise<string> { const [ivHex, dataHex] = ciphertext.split(':'); const iv = Buffer.from(ivHex, 'hex'); const data = Buffer.from(dataHex, 'hex');
const key = await webcrypto.subtle.importKey( 'raw', KEY, ALGORITHM, false, ['decrypt'] );
const decrypted = await webcrypto.subtle.decrypt( { name: ALGORITHM, iv }, key, data );
return new TextDecoder().decode(decrypted);}Las contraseñas nunca se almacenan cifradas (reversible), sino hasheadas (irreversible):
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 10;
export async function hashPassword(plain: string): Promise<string> { return await bcrypt.hash(plain, SALT_ROUNDS);}
export async function verifyPassword(plain: string, hash: string): Promise<boolean> { return await bcrypt.compare(plain, hash);}| Configuración | Valor |
|---|---|
| Algoritmo | bcrypt |
| Cost Factor | 10 (mínimo) |
| Salt | Auto-generado por usuario |
Resumen de Controles por Nivel
Section titled “Resumen de Controles por Nivel”| Nivel de Dato | Cifrado Tránsito | Cifrado Reposo | Acceso | Logging |
|---|---|---|---|---|
| 🟢 Público | ✅ HTTPS | ❌ | Libre | Normal |
| 🟡 Interno | ✅ HTTPS | ❌ | Autenticado | Normal |
| 🟠 Confidencial | ✅ HTTPS | ❌ | RBAC | Auditado |
| 🔴 Crítico | ✅ HTTPS | ✅ AES-256 | Restringido | Enmascarado |