Deja de Usar console.log() Así: Es una Fuga de Datos Esperando Pasar
Cada día, desarrolladores exponen tokens, passwords y datos personales sin darse cuenta. Este artículo explica los riesgos reales y las alternativas seguras.
El Problema con console.log()
console.log() es la herramienta de debugging más usada. Es rápida, no requiere imports, y funciona en cualquier ambiente JavaScript.
console.log("User data:", user);
Pero esa línea “inocente” puede haber filtrado el email, JWT token, session cookie, o datos de tarjeta de crédito del usuario a lugares que no controlas: el browser console, logs de cloud, o sistemas de terceros.
Esto no es un riesgo teórico. Muchos incidentes de seguridad — desde fugas internas hasta violaciones de compliance — comenzaron con algo tan simple como console.log(user).
Los desarrolladores ven console.log como “solo debugging”, pero la realidad es más complicada. En pipelines modernos de CI/CD y cloud, los logs persisten, se replican, y a veces se comparten con sistemas externos. Lo que empezó como “debugging temporal” puede convertirse en exposición permanente.
¿Dónde Terminan Tus Logs?
Un console.log() puede terminar en múltiples lugares, cada uno con sus propios riesgos:
1. Browser Console
En código frontend, cualquier usuario con acceso a DevTools (F12) puede ver lo que logueas:
- Tokens de autenticación
- Respuestas de API
- Variables de configuración
- Datos de sesión
Además, muchas herramientas de analytics y bug tracking scrapean errores de consola automáticamente, enviando tus logs a servicios externos.
2. Server Logs
En Node.js, el output de console.log() termina en:
- CloudWatch (AWS)
- Stackdriver (GCP)
- Azure Monitor
- Docker container logs
- Artifacts de CI/CD
Estos logs pueden vivir semanas o meses, indexados y respaldados — accesibles para personas fuera de tu perímetro de seguridad (contratistas, SREs, equipos de ops externos).
console.log("User login:", req.body);
Esa línea puede haber creado una copia permanente de un password en cloud storage.
3. Log Aggregation
En arquitecturas de microservicios, los logs fluyen por múltiples capas — a veces sin encriptar — antes de llegar a ELK Stack, Datadog, o Splunk.
Cada hop es un punto de exposición potencial. Y una vez que los datos llegan a un agregador, es extremadamente difícil eliminarlos selectivamente.
Por Qué console.log() Es Fundamentalmente Inseguro
1. Sin Clasificación de Datos
console.log no distingue entre datos sensibles y no sensibles. Logueas objetos completos, que frecuentemente contienen datos anidados con secretos:
console.log(user);
// Output real:
{
id: "u1234",
email: "alice@company.com",
token: "eyJhbGciOi...", // JWT expuesto
password: "hashedButStillBad" // Hash visible
}
2. Sin Control de Acceso
Una vez impreso, el log está abierto para cualquiera que pueda leerlo — devs, ops, usuarios (en browser). No hay modelo de permisos ni sistema de redacción.
3. Sin Gestión de Ciclo de Vida
Los logs son “para siempre”. Incluso logs de debugging “temporales” persisten en artifacts de CI/CD, containers Docker, o logs serverless.
Sin políticas de retención o sanitización, no puedes garantizar eliminación de datos — un problema directo de compliance bajo GDPR, SOC 2, y HIPAA.
Cómo Atacantes Explotan Logs
Los atacantes aman los logs porque los logs dicen la verdad. Si tu código loguea demasiado, esencialmente se auto-documenta para quien gane acceso.
Token Harvesting
Si los logs contienen JWTs o API tokens, un atacante con acceso a logs puede autenticarse como usuarios reales. Incluso tokens expirados revelan estructura interna (user IDs, algoritmos de firma).
Error Message Leaks
Errores mal manejados pueden exponer stack traces, paths internos, o queries SQL:
console.error("DB Error:", error);
// Si error contiene el query raw, acabas de regalar detalles del schema
Movimiento Lateral
En sistemas complejos, microservicios internos comparten logs. Un servicio comprometido puede contener credenciales o endpoints internos en sus logs, dando puntos de pivot para moverse lateralmente.
Ingeniería Social
Los logs a veces capturan emails internos, IPs, o variables de entorno. Los atacantes pueden usar esto como base para phishing o credential stuffing.
Logging Seguro: Los Principios
Logging seguro no es evitar logs — es controlar qué se loguea, cómo, y dónde.
1. Sanitización y Masking
Antes de escribir cualquier log, sanitiza. Enmascara campos sensibles como passwords, tokens, o PII:
function sanitize(obj) {
const clone = { ...obj };
const sensitiveFields = ['password', 'token', 'apiKey', 'secret', 'ssn', 'creditCard'];
for (const field of sensitiveFields) {
if (clone[field]) clone[field] = '[REDACTED]';
}
// Sanitizar campos anidados comunes
if (clone.user?.password) clone.user.password = '[REDACTED]';
if (clone.headers?.authorization) clone.headers.authorization = '[REDACTED]';
return clone;
}
// Uso
console.log("Request:", sanitize(req.body));
2. Log Levels
Usa niveles estandarizados (debug, info, warn, error) para separar ambientes:
| Level | Ambiente | Contenido |
|---|---|---|
debug |
Solo local dev | Datos completos para debugging |
info |
Todos | Logs operacionales no-sensibles |
warn |
Todos | Situaciones anómalas |
error |
Todos | Excepciones sanitizadas |
Un framework de logging puede automáticamente suprimir debug en producción.
3. Logging Estructurado
En lugar de texto libre, usa JSON estructurado:
// ❌ Malo: texto libre
console.log("User " + userId + " logged in from " + ip);
// ✅ Bueno: JSON estructurado
logger.info({
event: 'user_login',
userId: userId,
ip: maskIP(ip),
timestamp: new Date().toISOString()
});
Esto hace los logs machine-readable y más fáciles de filtrar/sanitizar.
4. Control de Acceso Centralizado
Almacena logs en sistemas controlados:
- Elasticsearch + Kibana (con restricciones de acceso)
- Datadog, Splunk, Loki
- Cloud-native (AWS CloudWatch, GCP Logging)
Asegura:
- HTTPS/TLS en tránsito
- Role-based access control
- Límites de retención
Implementación Práctica
Winston (Node.js)
const winston = require('winston');
// Función de sanitización
const sanitizeFormat = winston.format((info) => {
const sensitiveKeys = ['password', 'token', 'apiKey', 'authorization'];
const sanitize = (obj) => {
if (typeof obj !== 'object' || obj === null) return obj;
const result = Array.isArray(obj) ? [] : {};
for (const [key, value] of Object.entries(obj)) {
if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
result[key] = '[REDACTED]';
} else if (typeof value === 'object') {
result[key] = sanitize(value);
} else {
result[key] = value;
}
}
return result;
};
return sanitize(info);
});
// Crear logger
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: winston.format.combine(
sanitizeFormat(),
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
// En producción, agregar transports a sistemas centralizados
]
});
// Uso
logger.info('User login', { userId: 'u1234', ip: '192.168.1.1' });
logger.debug('Full request', { body: req.body }); // Solo en dev
Pino (Node.js - más rápido)
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
redact: {
paths: ['password', 'token', '*.password', '*.token', 'headers.authorization'],
censor: '[REDACTED]'
},
// Solo en desarrollo
transport: process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' }
: undefined
});
logger.info({ event: 'user_login', userId: 'u1234' });
Logging Condicional por Ambiente
// Opción simple
if (process.env.NODE_ENV !== 'production') {
console.log('Debug:', data);
}
// Mejor: usar una función wrapper
const debug = (...args) => {
if (process.env.NODE_ENV !== 'production') {
console.log('[DEBUG]', ...args);
}
};
debug('User object:', user);
Automatización: Prevenir console.log en Producción
ESLint
// .eslintrc.js
module.exports = {
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn'
}
};
Pre-commit Hook (Husky)
# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Buscar console.log en archivos staged
if git diff --cached --name-only | xargs grep -l 'console.log' 2>/dev/null; then
echo "❌ Error: console.log encontrado en archivos staged"
echo "Usa el logger estructurado en lugar de console.log"
exit 1
fi
CI/CD Gate
# .github/workflows/lint.yml
- name: Check for console.log
run: |
if grep -r "console.log" --include="*.js" --include="*.ts" src/; then
echo "::error::console.log encontrado en código fuente"
exit 1
fi
Checklist de Logging Seguro
Resumen de Acciones
## Nunca Loguear
- [ ] Passwords / credentials
- [ ] Tokens JWT / API keys
- [ ] PII (email, nombre, dirección)
- [ ] Datos financieros
- [ ] Variables de entorno sensibles
## Implementar
- [ ] Función de sanitización centralizada
- [ ] Log levels por ambiente (debug solo en dev)
- [ ] JSON estructurado en lugar de texto libre
- [ ] Framework de logging (Winston/Pino/Bunyan)
## Configurar
- [ ] Retención de logs (30-90 días máx)
- [ ] Rotación automática
- [ ] RBAC en sistemas de logs
- [ ] Encriptación en tránsito
## Automatizar
- [ ] ESLint rule: no-console
- [ ] Pre-commit hook
- [ ] CI/CD gate
- [ ] Scanner de datos sensibles
Herramientas Recomendadas
| Herramienta | Tipo | Uso |
|---|---|---|
| Winston | Logger Node.js | Más popular, muchos transports |
| Pino | Logger Node.js | Más rápido, redaction built-in |
| Bunyan | Logger Node.js | JSON by default |
| ESLint | Linter | Regla no-console |
| Husky | Git hooks | Pre-commit validation |
| Datadog | SaaS | Log aggregation + sensitive data scanner |
| AWS CloudWatch | Cloud | Logs con retention policies |
Caso de Estudio: La Fuga Silenciosa
El Setup
Una fintech construye un dashboard para préstamos. Durante desarrollo:
// Frontend
console.log("Loan request payload:", requestBody);
// Backend
console.log("Loan response:", response);
Ambos “temporales” para debugging.
El Incidente
Seis meses después, la app está en producción. Los logs — ahora en un sistema centralizado — contienen:
{
"loanAmount": 50000,
"userEmail": "cliente@empresa.com",
"bankAccount": "******9821",
"ssn": "123-45-6789"
}
Un permiso mal configurado en el log viewer dio acceso read-only a contratistas externos.
Por varios meses, datos financieros personales estuvieron visibles para terceros.
Resultado: Notificación de breach bajo GDPR, multa potencial, daño reputacional.
La Solución
- Reemplazar todos los
console.logcon Winston estructurado - Mascarar campos sensibles en ingestion
- Política de retención de 30 días
- Regla ESLint para bloquear
console.log()en producción
Conclusión
console.log() no es el enemigo. El logging descontrolado sí lo es.
La diferencia entre console.log() y logging seguro no es sintaxis — es intención. Uno es conveniencia temporal; el otro es observabilidad deliberada.
El software moderno corre en ambientes distribuidos, monitoreados, y frecuentemente regulados. Cada línea que imprimes se convierte en un registro que alguien puede leer — o abusar.
La próxima vez que uses console.log(), pregúntate:
“¿Estaría bien si estos datos terminaran en un dashboard público?”
Si no — ya sabes qué hacer.
Loguea menos. Loguea inteligente. Haz tu consola más silenciosa — pero mucho más segura.
Recursos
- Winston Documentation
- Pino - Fast JSON Logger
- ESLint no-console Rule
- OWASP Logging Cheat Sheet
- GDPR Requirements for Logging
Publicado en yoDEV.dev — La comunidad de desarrolladores de Latinoamérica


