nutriflow/api-v2
Scan completado con hallazgos críticos Rama: feature/user-profiles PR #47

Informe de Seguridad — NutriFlow API v2.0

Escaneo estático con Semgrep · 11 hallazgos en 847 líneas analizadas · 5 reglas personalizadas + rulesets OWASP

Node.js Express React PostgreSQL Docker OWASP Top 10
Escaneado: 16 jun 2026 · 09:42 UTC
Duración: 3.2 segundos
⛔ CI bloqueado — corrección requerida
PR #47 bloqueada — Se detectaron 7 hallazgos de severidad ERROR. Corrige las vulnerabilidades antes de hacer merge a main.
Errores críticos
7
Bloquean el merge
Advertencias
3
No bloquean CI
Informativos
1
Best practices
Archivos afectados
6
de 23 escaneados
Security Score
32/100
32%
🔴 Errores de Seguridad — ERROR 7 hallazgos
ERROR raw-sql-with-user-input Inyección SQL — concatenación directa de input de usuario CWE-89 src/api/users.js:34
Input del usuario (req.query.userId) se concatena directamente en una query SQL. Un atacante puede modificar la query para extraer, modificar o eliminar datos de la base de datos de pacientes de NutriFlow. Usa consultas parametrizadas.
src/api/users.js JavaScript líneas 31–38
31 async function getUserProfile(req, res) {
32 const userId = req.query.userId;
33 // BUG: concatenación directa sin sanitizar
34 const result = await db.query(`SELECT * FROM users WHERE id = ${userId}`);
35 res.json(result.rows);
36 }
✅ Fix sugerido — consulta parametrizada
- const result = await db.query(`SELECT * FROM users WHERE id = ${userId}`); + const result = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
📋 OWASP A03:2021 — Injection 🎯 Confianza: HIGH 🔧 Auto-fix disponible
ERROR hardcoded-secret Secret hardcodeado — API key de Stripe en código fuente CWE-798 config/payment.js:8
Una API key de Stripe de producción está hardcodeada en el código fuente. Si este repositorio es público (o un atacante obtiene acceso), las claves pueden ser usadas para realizar cargos fraudulentos en nombre de NutriFlow. Usa variables de entorno.
config/payment.js JavaScript líneas 6–12
6 const stripe = require('stripe');
7
8const stripeClient = stripe("sk_live_9xKmPqR4vL2wNzT8...");
9
10const webhookSecret = "whsec_AbCdEfGhIjKlMnOpQrSt";
11
12 module.exports = { stripeClient, webhookSecret };
✅ Fix sugerido — variables de entorno
- const stripeClient = stripe("sk_live_9xKmPqR4vL2wNzT8..."); - const webhookSecret = "whsec_AbCdEfGhIjKlMnOpQrSt"; + const stripeClient = stripe(process.env.STRIPE_SECRET_KEY); + const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
📋 OWASP A02:2021 — Cryptographic Failures 🎯 Confianza: HIGH ⚠️ Revocar la key inmediatamente en Stripe Dashboard
ERROR missing-auth-middleware Endpoint sin autenticación — /api/patients/records expuesto CWE-306 src/api/patients.js:22
El endpoint /api/patients/records que devuelve historial nutricional de pacientes no tiene middleware de autenticación. Cualquier petición sin token puede acceder a datos sensibles de salud (posible violación GDPR/HIPAA).
src/api/patients.js JavaScript líneas 20–26
20 // Historial nutricional — DATOS SENSIBLES
21router.get('/records', async (req, res) => {
22 const records = await PatientRecord.findAll();
23 res.json(records);
24 });
✅ Fix sugerido — añadir authMiddleware
- router.get('/records', async (req, res) => { + router.get('/records', authMiddleware, requireRole('dietist'), async (req, res) => {
📋 OWASP A01:2021 — Broken Access Control 🎯 Confianza: HIGH ⚖️ Riesgo GDPR — datos de salud
ERROR dangerous-html XSS — dangerouslySetInnerHTML con contenido del usuario CWE-79 src/components/NutritionNotes.tsx:57
El componente NutritionNotes renderiza notas clínicas del dietista directamente como HTML. Si un atacante inyecta JavaScript en las notas, se ejecutará en el navegador de todos los pacientes que las vean.
src/components/NutritionNotes.tsx TypeScript/React líneas 54–60
54 return (
55 <div className="notes-container">
56 <div dangerouslySetInnerHTML={{__html: note.content}} />
57 </div>
58 );
✅ Fix sugerido — sanitizar con DOMPurify
+ import DOMPurify from 'dompurify'; - <div dangerouslySetInnerHTML={{__html: note.content}} /> + <div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(note.content)}} />
📋 OWASP A03:2021 — Injection (XSS) 🎯 Confianza: HIGH 📦 npm install dompurify @types/dompurify
🟡 Advertencias — WARNING 3 hallazgos
WARNING missing-rel-noopener Tab-napping — enlaces externos sin rel="noopener noreferrer" CWE-1022 src/components/ResourceLinks.tsx:31
3 instancias de <a target="_blank"> sin rel="noopener noreferrer". La página de destino puede acceder a window.opener y redirigir la pestaña original a una URL maliciosa.
📋 OWASP A05:2021 🎯 Confianza: HIGH 🔧 Auto-fix disponible
WARNING console-log-sensitive Log de datos sensibles — console.log con objeto patient CWE-532 src/api/auth.js:89
Se hace console.log(patient) incluyendo campos como password_hash, dob y medical_id. Los logs de producción son accesibles por el equipo y posibles atacantes.
📋 OWASP A09:2021 — Security Logging 🎯 Confianza: MEDIUM
⚙️ GitHub Actions — .github/workflows/security.yml
name: Security Scan on: pull_request: branches: [main, develop] jobs: semgrep: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Semgrep Scan uses: semgrep/semgrep-action@v1 with: config: >- p/security-audit p/owasp-top-ten .semgrep/ generateSarif: "1" env: SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - name: Upload SARIF to GitHub uses: github/codeql-action/upload-sarif@v3 with: sarif_file: semgrep.sarif
📏 Reglas personalizadas — NutriFlow
Regla Sev. Hits CWE
raw-sql-with-user-input ERROR 3 CWE-89
hardcoded-secret ERROR 2 CWE-798
missing-auth-middleware ERROR 2 CWE-306
dangerous-html ERROR 1 CWE-79
missing-rel-noopener WARN 3 CWE-1022
console-log-sensitive WARN 2 CWE-532
no-eval INFO 0 CWE-95
🛡️ Plan de Acción Prioritario
🔑

Revocar secrets inmediatamente

La API key de Stripe detectada debe revocarse en el dashboard de Stripe AHORA, incluso antes de hacer el fix en código. Asumir que la clave está comprometida.

Crítico — Hoy
🛡️

Parametrizar todas las queries SQL

Reemplazar las 3 instancias de SQL concatenado por consultas parametrizadas. Ejecutar semgrep --fix para aplicar los auto-fixes generados.

Crítico — 24h
🔐

Middleware de autenticación global

Aplicar authMiddleware a nivel de router en lugar de endpoint por endpoint. Añadir test de integración que verifique 401 en cada ruta protegida.

Alto — 48h
🧹

Sanitizar HTML con DOMPurify

Instalar DOMPurify y envolver todos los usos de dangerouslySetInnerHTML. Crear un componente SafeHtml reutilizable para el equipo.

Alto — 48h
🔄

Secretos en variables de entorno

Migrar todos los secretos a .env + GitHub Secrets. Añadir .env al .gitignore. Usar dotenv en desarrollo y Secrets Manager en producción.

Alto — 72h
📊

Semgrep Cloud Dashboard

Activar Semgrep Cloud (plan gratuito) para tracking histórico, triage de falsos positivos y métricas de deuda de seguridad a lo largo del tiempo.

Medio — 1 semana