Plataforma técnica · Seguridad
Protección de Datos
Seguridad
La protección de datos define los controles mínimos para manejar información financiera, tributaria, laboral y personal dentro del ecosistema Nostromo. Todo dato debe clasificarse antes de ser almacenado, procesado, expuesto en API o registrado en logs.
Clasificación de Datos
Section titled “Clasificación de Datos”| Nivel | Descripción | Ejemplos | Controles mínimos |
|---|---|---|---|
| Público | Información destinada a difusión externa | Documentación pública, contenido del sitio | Revisión editorial |
| Interno | Información operativa sin sensibilidad directa | Configuración de UI, identificadores técnicos no personales | Autenticación requerida |
| Confidencial | Información sensible del negocio o del tenant | Contratos, saldos, movimientos contables, reportes financieros | RBAC, auditoría de acceso, segregación por tenant |
| Crítico | PII, secretos y datos laborales sensibles | RUT, remuneraciones, claves, tokens, llaves criptográficas | Cifrado, acceso restringido, logs enmascarados |
Manejo de Secretos
Section titled “Manejo de Secretos”- No versionar credenciales, tokens, llaves privadas ni archivos
.env. - Usar
.env.localpara desarrollo local y mantenerlo fuera del repositorio. - Inyectar secretos de producción mediante variables de entorno administradas por la plataforma.
- Rotar secretos críticos, como
$DB_PASSWORD,$JWT_SECRETy$ENCRYPTION_KEY, al menos cada 90 días. - Revocar y reemplazar secretos inmediatamente ante sospecha de exposición.
Enmascaramiento en Logs
Section titled “Enmascaramiento en Logs”Los logs deben permitir diagnosticar fallas sin exponer información crítica. Antes de registrar objetos de dominio, se deben enmascarar claves sensibles y estructuras anidadas.
const SENSITIVE_KEYS = [ 'password', 'token', 'secret', 'apiKey', 'api_key', 'rut', 'salary', 'sueldo', 'clave', 'authorization',];
export function sanitizeLog(value: unknown): unknown { if (Array.isArray(value)) { return value.map((item) => sanitizeLog(item)); }
if (value && typeof value === 'object') { return Object.fromEntries( Object.entries(value as Record<string, unknown>).map(([key, nestedValue]) => { const isSensitive = SENSITIVE_KEYS.some((sensitiveKey) => key.toLowerCase().includes(sensitiveKey), );
return [key, isSensitive ? '[REDACTED]' : sanitizeLog(nestedValue)]; }), ); }
return value;}
console.log( sanitizeLog({ userId: '$USER_ID', rut: '$RUT', authorization: 'Bearer $TOKEN', }),);Estrategias de Cifrado
Section titled “Estrategias de Cifrado”El sistema aplica controles por capa para proteger datos en tránsito, en reposo y durante la verificación de credenciales.
Todo tráfico HTTP público debe operar exclusivamente sobre HTTPS.
| Control | Configuración requerida |
|---|---|
| HSTS | max-age=31536000; includeSubDomains |
| Certificados | Gestionados por la plataforma de despliegue |
| TLS | Versión mínima 1.2; versión preferida 1.3 |
Strict-Transport-Security: max-age=31536000; includeSubDomainsX-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originLa base de datos debe contar con cifrado de almacenamiento provisto por la infraestructura. Para campos críticos, se debe aplicar cifrado adicional a nivel de aplicación.
import { webcrypto } from 'node:crypto';
const ALGORITHM = 'AES-GCM';const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex');
export async function encrypt(plainText: string): Promise<string> { const iv = webcrypto.getRandomValues(new Uint8Array(12)); const encoded = new TextEncoder().encode(plainText);
const key = await webcrypto.subtle.importKey( 'raw', KEY, ALGORITHM, false, ['encrypt'], );
const encrypted = await webcrypto.subtle.encrypt( { name: ALGORITHM, iv }, key, encoded, );
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 no se almacenan cifradas de forma reversible. Se deben almacenar como hashes con salt individual por usuario.
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
export async function hashPassword(plainText: string): Promise<string> { return bcrypt.hash(plainText, SALT_ROUNDS);}
export async function verifyPassword( plainText: string, hash: string,): Promise<boolean> { return bcrypt.compare(plainText, hash);}| Configuración | Valor mínimo |
|---|---|
| Algoritmo | bcrypt |
| Cost factor | 12 |
| Salt | Generado por usuario |
Controles por Nivel
Section titled “Controles por Nivel”| Nivel de dato | Cifrado en tránsito | Cifrado en reposo | Acceso | Logging |
|---|---|---|---|---|
| Público | Requerido | No requerido | Libre o autenticado según recurso | Normal |
| Interno | Requerido | No requerido | Autenticado | Normal |
| Confidencial | Requerido | Según criticidad del campo | RBAC y tenant scope | Auditado |
| Crítico | Requerido | Requerido | Restringido y justificado | Enmascarado |
Revisión Antes de Merge
Section titled “Revisión Antes de Merge”| Verificación | Criterio de aceptación |
|---|---|
| Secretos | No existen valores reales en código, documentación ni fixtures |
| PII | Los ejemplos usan placeholders como $RUT, $USER_ID o $TOKEN |
| Logs | Los objetos sensibles pasan por sanitización antes de registrarse |
| Cifrado | Los campos críticos declaran mecanismo de protección |
| Acceso | Las rutas que exponen datos confidenciales validan autenticación, RBAC y tenant scope |