Los 10 Riesgos Principales
Broken Access Control
Vector de ataque
Explotación de fallos en la implementación de restricciones sobre lo que los usuarios autenticados pueden hacer.
Debilidad
Controles de acceso mal implementados o ausentes que permiten a los atacantes acceder, modificar o eliminar datos no autorizados.
Impacto
Acceso no autorizado a funcionalidades, datos sensibles o cuentas de otros usuarios.
Estrategias de mitigación
- Implementar un modelo de control de acceso consistente con denegación por defecto.
- Validar permisos en cada solicitud, no solo en el frontend.
- Desactivar el listado de directorios y asegurar que los metadatos de archivos no sean accesibles.
- Registrar fallos de control de acceso y alertar a los administradores cuando sea apropiado.
Ejemplo de código vulnerable
// Código vulnerable
// GET /user/:id
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
const user = db.getUserById(userId);
if (!user) return res.status(404).send("User not found");
// ❌ Devuelve cualquier usuario, sin validar si el solicitante tiene acceso
res.json(user);
});// Código seguro
// GET /user/:id
app.get('/user/:id', (req, res) => {
const requestedId = parseInt(req.params.id);
const authenticatedId = req.user.id;
if (requestedId !== authenticatedId && req.user.role !== 'admin') {
return res.status(403).send("Access denied");
}
const user = db.getUserById(requestedId);
if (!user) return res.status(404).send("User not found");
res.json(user);
});// Código vulnerable
// Frontend (React o HTML/JS)
if (user.role === 'admin') {
document.getElementById("adminPanel").style.display = "block";
}
// Backend sin verificación
app.get('/admin/data', (req, res) => {
// ❌ No valida el rol del usuario
res.send("Admin info: user list, settings...");
});// Código seguro
app.get('/admin/data', (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).send("Forbidden");
}
res.send("Admin info: user list, settings...");
});// Código vulnerable
// DELETE /comment/:id
app.delete('/comment/:id', (req, res) => {
const comment = db.getCommentById(req.params.id);
if (!comment) return res.status(404).send("Not found");
// ❌ Cualquiera puede borrar cualquier comentario
db.deleteComment(comment.id);
res.send("Comment deleted");
});// Código seguro
// DELETE /comment/:id
app.delete('/comment/:id', (req, res) => {
const comment = db.getCommentById(req.params.id);
if (!comment) return res.status(404).send("Not found");
if (comment.ownerId !== req.user.id && req.user.role !== 'admin') {
return res.status(403).send("Unauthorized");
}
db.deleteComment(comment.id);
res.send("Comment deleted");
});Cryptographic Failures
Vector de ataque
Explotación de fallos en la implementación de cifrado o ausencia del mismo para acceder a datos sensibles.
Debilidad
Datos sensibles transmitidos o almacenados sin cifrado adecuado, uso de algoritmos obsoletos o claves débiles.
Impacto
Exposición de información sensible como credenciales, datos personales, tarjetas de crédito o información médica.
Estrategias de mitigación
- Clasificar los datos procesados, almacenados o transmitidos por la aplicación.
- Cifrar todos los datos sensibles en reposo y en tránsito.
- Utilizar algoritmos de cifrado fuertes y actualizados.
- Almacenar contraseñas usando funciones de hashing seguras (bcrypt, Argon2).
- Deshabilitar el almacenamiento en caché para respuestas que contengan datos sensibles.
Ejemplo de código vulnerable
// Código vulnerable
// Registro de usuario (guarda la contraseña en texto plano)
app.post('/register', (req, res) => {
const { username, password } = req.body;
// ❌ ¡Contraseña almacenada directamente!
db.saveUser({ username, password });
res.send("User registered");
});// Código seguro
const bcrypt = require('bcrypt');
// Registro de usuario con hash
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
db.saveUser({ username, password: hashedPassword });
res.send("User registered securely");
});// Código vulnerable
const crypto = require('crypto');
// Cifrado usando algoritmo obsoleto (DES)
function encrypt(text) {
const cipher = crypto.createCipher('des', 'secret-key');
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}// Código seguro
const crypto = require('crypto');
// Cifrado fuerte usando AES-256-GCM
function encrypt(text, key, iv) {
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return { encrypted, iv, tag };
}// Código vulnerable
const jwt = require('jsonwebtoken');
// Verifica el token pero acepta cualquier algoritmo
function verifyToken(token) {
// ❌ No especifica algoritmo, acepta "none"
return jwt.verify(token, 'secret');
}// Código seguro
const jwt = require('jsonwebtoken');
// Verifica el token especificando algoritmo seguro
function verifyToken(token) {
return jwt.verify(token, 'secret', { algorithms: ['HS256'] });
}Injection
Vector de ataque
Envío de datos hostiles a un intérprete como parte de un comando o consulta.
Debilidad
Falta de validación, sanitización o parametrización de entradas del usuario en consultas SQL, comandos OS, XML, etc.
Impacto
Pérdida o corrupción de datos, divulgación de información sensible, denegación de acceso o incluso control completo del servidor.
Estrategias de mitigación
- Usar consultas parametrizadas o preparadas.
- Validar todas las entradas del lado del servidor.
- Utilizar ORM (Object Relational Mapping) con parámetros vinculados.
- Implementar listas blancas para validación de entradas.
- Limitar los privilegios de las cuentas de base de datos utilizadas por las aplicaciones.
Ejemplo de código vulnerable
// Código vulnerable
// GET /user?username=admin
app.get('/user', (req, res) => {
const username = req.query.username;
// ❌ Consulta construida con interpolación directa
const query = `SELECT * FROM users WHERE username = '${username}'`;
db.query(query, (err, results) => {
res.json(results);
});
});// Código seguro
// ✅ Consulta parametrizada
app.get('/user', (req, res) => {
const username = req.query.username;
const query = `SELECT * FROM users WHERE username = ?`;
db.query(query, [username], (err, results) => {
res.json(results);
});
});// Código vulnerable
const { exec } = require('child_process');
// Ejecuta ping a un host ingresado por el usuario
app.get('/ping', (req, res) => {
const host = req.query.host;
// ❌ Vulnerable a command injection (ej: ?host=google.com;rm -rf /)
exec(`ping -c 1 ${host}`, (err, stdout, stderr) => {
res.send(stdout || stderr);
});
});// Código seguro
const { spawn } = require('child_process');
// Usa spawn con argumentos separados y validación
app.get('/ping', (req, res) => {
const host = req.query.host;
if (!/^[a-zA-Z0-9.-]+$/.test(host)) {
return res.status(400).send("Invalid host");
}
const ping = spawn('ping', ['-c', '1', host]);
let output = '';
ping.stdout.on('data', (data) => output += data);
ping.on('close', () => res.send(output));
});// Código vulnerable
// MongoDB - inicio de sesión
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// ❌ Un atacante puede enviar: { "username": { "$ne": null }, "password": { "$ne": null } }
const user = await db.collection('users').findOne({ username, password });
if (!user) return res.status(401).send("Invalid credentials");
res.send("Welcome!");
});// Código seguro
app.post('/login', async (req, res) => {
const { username, password } = req.body;
if (typeof username !== 'string' || typeof password !== 'string') {
return res.status(400).send("Invalid input");
}
const user = await db.collection('users').findOne({ username, password });
if (!user) return res.status(401).send("Invalid credentials");
res.send("Welcome!");
});Insecure Design
Vector de ataque
Explotación de fallos en el diseño y la arquitectura de seguridad de la aplicación.
Debilidad
Ausencia de controles de seguridad desde la fase de diseño, falta de modelado de amenazas o diseño que no considera casos de abuso.
Impacto
Amplia gama de vulnerabilidades que pueden afectar todo el sistema, desde pérdida de datos hasta compromiso completo.
Estrategias de mitigación
- Establecer un ciclo de vida de desarrollo seguro (SDLC).
- Utilizar bibliotecas y herramientas seguras por diseño.
- Modelar amenazas para flujos críticos en la aplicación.
- Integrar consideraciones de seguridad en historias de usuario y casos de prueba.
- Implementar límites de recursos y controles a nivel de arquitectura.
Ejemplo de diseño inseguro
// Código vulnerable
// Frontend (HTML o React)
<button id="transferButton">Transferir $100</button>
// Backend (acepta cualquier monto)
app.post('/transfer', (req, res) => {
const { toUserId, amount } = req.body;
// ❌ No se valida si el usuario tiene saldo suficiente
db.transfer(req.user.id, toUserId, amount);
res.send("Transfer completed");
});// Código seguro
app.post('/transfer', async (req, res) => {
const { toUserId, amount } = req.body;
const balance = await db.getUserBalance(req.user.id);
if (amount > balance) {
return res.status(400).send("Insufficient funds");
}
await db.transfer(req.user.id, toUserId, amount);
res.send("Transfer completed");
});// Código vulnerable
// El usuario puede modificar productos desde el frontend
// PATCH /product/:id
app.patch('/product/:id', async (req, res) => {
const productId = req.params.id;
// ❌ No valida si el usuario es el dueño o tiene permisos
await db.updateProduct(productId, req.body);
res.send("Product updated");
});// Código seguro
app.patch('/product/:id', async (req, res) => {
const productId = req.params.id;
const product = await db.getProductById(productId);
if (!product || product.ownerId !== req.user.id) {
return res.status(403).send("Forbidden");
}
await db.updateProduct(productId, req.body);
res.send("Product updated");
});// Código vulnerable
// Recuperación de contraseña por pregunta secreta
app.post('/recover', async (req, res) => {
const { username, answer } = req.body;
const user = await db.findUserByUsername(username);
// ❌ Usa una pregunta predecible, sin límite de intentos
if (user.securityAnswer === answer) {
res.send(`Tu contraseña es: ${user.password}`);
} else {
res.status(401).send("Incorrect answer");
}
});// Código seguro
// Envío de link de recuperación temporal con token
app.post('/recover', async (req, res) => {
const { email } = req.body;
const user = await db.findUserByEmail(email);
if (!user) return res.send("If this email exists, a link was sent");
const token = generateSecureToken(); // con expiración
await db.saveRecoveryToken(user.id, token);
await sendEmail(email, `Recover your account: https://app.com/reset?token=${token}`);
res.send("If this email exists, a link was sent");
});Security Misconfiguration
Vector de ataque
Explotación de sistemas mal configurados, incompletos o con configuraciones por defecto.
Debilidad
Configuraciones de seguridad incorrectas, servicios innecesarios habilitados, permisos incorrectos, etc.
Impacto
Acceso no autorizado a datos o funcionalidades del sistema, hasta compromiso completo del servidor.
Estrategias de mitigación
- Implementar un proceso de endurecimiento de seguridad para todos los entornos.
- Eliminar o no instalar características, componentes o servicios no utilizados.
- Revisar y actualizar configuraciones como parte del proceso de gestión de parches.
- Implementar una arquitectura de segmentación con separación entre componentes.
- Enviar directivas de seguridad a los clientes (headers de seguridad).
Ejemplo de configuración segura
// Código vulnerable
// Express sin ningún header de seguridad
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send("Hello world");
});
app.listen(3000);// Código seguro
// Express usando helmet para agregar headers seguros
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet()); // ✅ Activa varios headers de seguridad
app.get('/', (req, res) => {
res.send("Hello world");
});
app.listen(3000);// Código vulnerable
// Express con stack traces en producción
app.use((err, req, res, next) => {
// ❌ Devuelve detalles del error al cliente
res.status(500).send(err.stack);
});// Código seguro
// Manejo de errores controlado
app.use((err, req, res, next) => {
// ✅ No revela información sensible en producción
console.error(err); // log interno
res.status(500).send("Something went wrong");
});// Código vulnerable
// Rutas administrativas sin protección
app.use('/admin', require('./adminRoutes'));
// adminRoutes.js
router.get('/dashboard', (req, res) => {
res.send("Admin Panel");
});
// ⚠️ Si un atacante descubre /admin, puede acceder libremente.// Código seguro
// Middleware de autenticación para rutas de admin
function isAdmin(req, res, next) {
if (req.user && req.user.role === 'admin') return next();
return res.status(403).send("Forbidden");
}
app.use('/admin', isAdmin, require('./adminRoutes'));Vulnerable and Outdated Components
Vector de ataque
Explotación de vulnerabilidades conocidas en componentes desactualizados o sin parches.
Debilidad
Uso de bibliotecas, frameworks o componentes con vulnerabilidades conocidas o sin soporte.
Impacto
Desde pérdida de datos hasta compromiso completo del servidor, dependiendo de la vulnerabilidad explotada.
Estrategias de mitigación
- Eliminar dependencias, características y archivos no utilizados.
- Mantener un inventario de componentes y sus versiones.
- Monitorear fuentes como CVE y boletines de seguridad.
- Utilizar herramientas de análisis de composición de software.
- Obtener componentes únicamente de fuentes oficiales y verificar su integridad.
Ejemplo de gestión de dependencias
// Código vulnerable
<!-- jQuery 1.6.2 contiene vulnerabilidades XSS conocidas -->
<script src="https://code.jquery.com/jquery-1.6.2.min.js"></script>
<script>
const param = new URLSearchParams(window.location.search).get("msg");
$('#output').html(param); // ❌ vulnerable a XSS
</script>// Código seguro
<!-- jQuery actualizado -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
const param = new URLSearchParams(window.location.search).get("msg");
$('#output').text(param); // ✅ evita ejecución de HTML
</script>// Código vulnerable
{
"dependencies": {
"lodash": "4.17.20" // La versión 4.17.20 tiene vulnerabilidades de prototype pollution (CVE-2020-8203)
}
}// Código seguro
{
"dependencies": {
"lodash": "^4.17.21"
}
}
// Además, escanear con npm audit// Código vulnerable
# Imagen base desactualizada con múltiples CVEs
FROM node:14
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]// Código seguro
# Imagen base actualizada y escaneada
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]
# Puedes escanear con herramientas como Trivy, Docker Scout o SnykIdentification and Authentication Failures
Vector de ataque
Explotación de fallos en la autenticación para asumir la identidad de otros usuarios.
Debilidad
Implementación incorrecta de la autenticación, permitiendo ataques de fuerza bruta, reutilización de credenciales, etc.
Impacto
Compromiso de cuentas de usuario, desde usuarios normales hasta administradores, permitiendo robo de identidad.
Estrategias de mitigación
- Implementar autenticación multifactor cuando sea posible.
- No desplegar con credenciales por defecto.
- Implementar controles contra ataques de fuerza bruta.
- Validar la fortaleza de las contraseñas.
- Utilizar un gestor de sesiones seguro del lado del servidor.
Ejemplo de autenticación segura
// Código vulnerable
// Ruta de login sin protección contra fuerza bruta
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await db.findUser(username);
if (user && user.password === password) {
return res.send("Login successful");
}
res.status(401).send("Invalid credentials");
});// Código seguro
// Ruta protegida con limitador de intentos (usando express-rate-limit)
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 5, // máximo 5 intentos
message: "Too many login attempts, please try again later"
});
app.post('/login', loginLimiter, async (req, res) => {
// lógica de login
});// Código vulnerable
// Guardar contraseña sin hash (inseguro)
app.post('/register', async (req, res) => {
const { username, password } = req.body;
await db.createUser({ username, password }); // ❌ texto plano
res.send("User created");
});// Código seguro
// Usar bcrypt para hash de contraseñas
const bcrypt = require('bcrypt');
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const hash = await bcrypt.hash(password, 12); // ✅ hash seguro
await db.createUser({ username, password: hash });
res.send("User created");
});// Código vulnerable
// Crea un token simple y persistente
app.post('/login', async (req, res) => {
const { username } = req.body;
const token = Buffer.from(username).toString('base64'); // ❌ predecible
res.cookie('session', token);
res.send("Logged in");
});// Código seguro
// Usar JWT firmado, con expiración
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
app.post('/login', async (req, res) => {
const { username } = req.body;
const token = jwt.sign({ username }, SECRET, { expiresIn: '1h' }); // ✅ seguro
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'Strict'
});
res.send("Logged in");
});Software and Data Integrity Failures
Vector de ataque
Explotación de fallos en la verificación de integridad de software y datos.
Debilidad
Falta de verificación de integridad de software y datos, permitiendo actualizaciones no verificadas o manipulación de datos.
Impacto
Desde pérdida de integridad de datos hasta ejecución de código malicioso en servidores o clientes.
Estrategias de mitigación
- Utilizar firmas digitales para verificar la integridad de software.
- Asegurar que las dependencias se obtienen de repositorios confiables.
- Implementar revisión de código y aprobaciones múltiples para cambios.
- Asegurar que CI/CD tiene una adecuada segregación, configuración y control de acceso.
- Verificar la integridad de datos críticos mediante firmas o hashes.
Ejemplo de verificación de integridad
// Código vulnerable
<!-- Script externo sin verificación de integridad -->
<script src="https://cdn.example.com/library.js"></script>// Código seguro
<!-- Script con hash de integridad y CORS -->
<script
src="https://cdn.example.com/library.js"
integrity="sha384-AbCd123..."
crossorigin="anonymous">
</script>// Código vulnerable
// Descarga de código externo sin verificar fuente o firma
const https = require('https');
https.get("https://updates.example.com/update.js", res => {
res.pipe(fs.createWriteStream("update.js"));
});// Código seguro
// Verifica firma digital del archivo descargado
const crypto = require('crypto');
const fs = require('fs');
// Simulación: hashear y verificar la firma antes de usar
const expectedHash = "abcdef1234567890...";
const fileBuffer = fs.readFileSync("update.js");
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
if (hash === expectedHash) {
require('./update.js'); // ✅ solo si la integridad es válida
} else {
console.error("Update file is invalid or has been tampered with.");
}// Código vulnerable
# .github/workflows/deploy.yml
steps:
- name: Checkout repo
uses: actions/checkout@master # ❌ uso de branch mutable
- name: Deploy
run: ./deploy.sh// Código seguro
# Uso de un tag o commit específico, y firma de acción verificada
steps:
- name: Checkout repo
uses: actions/checkout@v3 # ✅ versión fija
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Verify deployment artifact
run: |
gpg --verify app.sig app.tar.gz # ✅ verifica la integridad antes de desplegar
- name: Deploy
run: ./deploy.shSecurity Logging and Monitoring Failures
Vector de ataque
Explotación de la falta de detección y respuesta a brechas de seguridad.
Debilidad
Registros insuficientes, falta de monitoreo o integración con sistemas de respuesta a incidentes.
Impacto
Incapacidad para detectar, responder o recuperarse de brechas activas, permitiendo ataques prolongados.
Estrategias de mitigación
- Implementar registro de eventos de seguridad relevantes.
- Asegurar que los registros incluyan suficiente contexto para análisis forense.
- Establecer monitoreo efectivo y alertas para actividades sospechosas.
- Crear un plan de respuesta a incidentes y recuperación.
- Utilizar herramientas SIEM (Security Information and Event Management).
Ejemplo de logging seguro
// Código vulnerable
app.post('/login', async (req, res) => {
const user = await db.findUser(req.body.username);
if (!user || user.password !== req.body.password) {
return res.status(401).send("Invalid credentials");
}
res.send("Logged in");
});// Código seguro
const logger = require('./logger'); // supongamos que hay un logger configurado
app.post('/login', async (req, res) => {
const user = await db.findUser(req.body.username);
if (!user || user.password !== req.body.password) {
logger.warn(`Failed login attempt for user: ${req.body.username}`);
return res.status(401).send("Invalid credentials");
}
logger.info(`User ${req.body.username} logged in`);
res.send("Logged in");
});// Código vulnerable
// El sistema registra internamente errores críticos pero nadie los revisa
logger.error("Too many failed login attempts");// Código seguro
// Envío de alertas y monitoreo proactivo
logger.error("Too many failed login attempts");
notifySecurityTeam("Multiple login failures detected from IP: " + req.ip);
// Ejemplo de integración con un sistema SIEM o notificación
function notifySecurityTeam(message) {
// Enviar a Slack, SIEM, correo, etc.
sendToMonitoringService({ level: "critical", message });
}// Código vulnerable
// Logs escritos directamente en archivos sin control de acceso
fs.appendFileSync('app.log', `User ${username} failed login`);// Código seguro
const winston = require('winston');
const fs = require('fs');
// Asegurarse de que los archivos tengan permisos restringidos
fs.chmodSync('secure.log', 0o600); // ✅ solo lectura/escritura para el dueño
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'secure.log' })
]
});
logger.warn("Login attempt failed", {
username: req.body.username,
ip: req.ip,
timestamp: new Date().toISOString()
});Server-Side Request Forgery (SSRF)
Vector de ataque
Manipulación del servidor para que realice solicitudes HTTP a destinos arbitrarios.
Debilidad
Falta de validación de URLs proporcionadas por el usuario antes de que el servidor realice solicitudes.
Impacto
Escaneo de puertos internos, acceso a servicios internos, exfiltración de datos o ejecución de código remoto.
Estrategias de mitigación
- Sanitizar y validar todas las entradas de datos del cliente.
- Implementar listas blancas de dominios/IPs permitidos.
- Bloquear tráfico a direcciones IP privadas y localhost.
- Implementar políticas de firewall para bloquear conexiones no esenciales.
- No enviar respuestas en bruto al cliente.
Ejemplo de mitigación de SSRF
// Código vulnerable
// El usuario puede hacer que el servidor haga peticiones arbitrarias
app.post('/fetch', async (req, res) => {
const { url } = req.body;
const response = await fetch(url); // ❌ SSRF posible
const data = await response.text();
res.send(data);
});// Código seguro
const allowedDomains = ['https://api.example.com', 'https://docs.example.com'];
app.post('/fetch', async (req, res) => {
const { url } = req.body;
if (!allowedDomains.some(domain => url.startsWith(domain))) {
return res.status(403).send("URL not allowed");
}
const response = await fetch(url); // ✅ acceso limitado
const data = await response.text();
res.send(data);
});// Código vulnerable
// Permite acceder a URLs internas como http://localhost/admin o 169.254.x.x
app.get('/image-proxy', async (req, res) => {
const { imageUrl } = req.query;
const response = await fetch(imageUrl);
const buffer = await response.arrayBuffer();
res.setHeader('Content-Type', 'image/jpeg');
res.send(Buffer.from(buffer));
});// Código seguro
const dns = require('dns').promises;
const { URL } = require('url');
async function isPrivateIp(urlString) {
try {
const url = new URL(urlString);
const addresses = await dns.lookup(url.hostname, { all: true });
return addresses.some(addr => {
return /^127./.test(addr.address) || // localhost
/^10./.test(addr.address) || // red privada
/^192.168./.test(addr.address) ||
/^172.(1[6-9]|2[0-9]|3[0-1])./.test(addr.address);
});
} catch (e) {
return true; // fallback a denegar
}
}
app.get('/image-proxy', async (req, res) => {
const { imageUrl } = req.query;
if (await isPrivateIp(imageUrl)) {
return res.status(403).send("Access to internal resources denied");
}
const response = await fetch(imageUrl);
const buffer = await response.arrayBuffer();
res.setHeader('Content-Type', 'image/jpeg');
res.send(Buffer.from(buffer));
});// Código vulnerable
// Recibe una URL de webhook sin validación
app.post('/register-webhook', async (req, res) => {
const { url } = req.body;
db.saveWebhook(url); // ❌ el servidor luego enviará datos a cualquier URL
res.send("Webhook saved");
});// Código seguro
const validWebhookDomain = /^https://hooks.example.com/.*/;
app.post('/register-webhook', async (req, res) => {
const { url } = req.body;
if (!validWebhookDomain.test(url)) {
return res.status(400).send("Invalid webhook URL");
}
db.saveWebhook(url); // ✅ solo dominios específicos permitidos
res.send("Webhook saved");
});