🛡️

Semgrep Security Audit

Análisis estático • Rulesets de terceros • SARIF consolidado

NutriFlow API
Generado: 2026-06-18 09:42 UTC · Duración: 4m 37s · 5.203 archivos
⚙ Semgrep Pro 1.75.0
7
Critical
18
High
34
Medium
51
Low
12
Info
122
Total
Modo ⚠ Important Only
Motor ✓ Semgrep Pro (cross-file)
Repositorio /tmp/nutriflow-api/
Lenguajes Python · TypeScript · Dockerfile · Terraform · YAML
Rulesets activos 27 rulesets (9 oficiales + 4 terceros + 14 infra)
Output static_analysis_semgrep_1/results/results.sarif
Hallazgos por Categoría Important Only · medium+high confidence
Secrets / Credentials
23
Injection (SQL/CMD)
19
Auth / Access Control
15
Insecure Crypto
12
SSRF / Path Traversal
11
IaC Misconfiguration
9
Container Security
8
XSS / CSRF
6
Dependency / Supply Chain
4
Lenguajes Detectados 5.203 archivos totales
Python 3.201 arch.
68
TypeScript 1.803 arch.
27
Dockerfile 48 arch.
11
Terraform 120 arch.
9
YAML (GH Actions) 35 arch.
7
Rulesets Activos
p/security-audit p/secrets p/owasp-top-ten p/python p/fastapi p/javascript p/typescript p/react p/nodejs p/dockerfile p/terraform p/github-actions trailofbits/semgrep-rules elttam/semgrep-rules apiiro/malicious-code
Hallazgos Críticos & Altos — 25 vulnerabilidades · acción inmediata requerida
Critical
AWS Secret Access Key hardcodeada en archivo de configuración
📄 app/core/config.py · línea 47 · p/secrets · AWS_SECRET_ACCESS_KEY_pattern
Una clave secreta de AWS IAM está hardcodeada directamente en el código fuente. Cualquiera con acceso al repositorio puede comprometer la cuenta AWS completa, incluyendo los datos de pacientes almacenados en S3 y las bases de datos RDS.
44class Settings(BaseSettings):
45 DATABASE_URL: str = "postgresql://admin:pass@localhost/nutriflow"
46 AWS_REGION: str = "eu-west-1"
47 AWS_SECRET_ACCESS_KEY: str = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
48 AWS_ACCESS_KEY_ID: str = "AKIAIOSFODNN7EXAMPLE"
49 S3_BUCKET_PATIENTS: str = "nutriflow-patient-data-prod"
Fix: Mueve las credenciales a variables de entorno (AWS_SECRET_ACCESS_KEY) y usa pydantic-settings con env_file=".env". Añade .env al .gitignore. Rota las claves inmediatamente en la consola AWS IAM.
CWE-798: Hard-coded Credentials OWASP A07:2021 — Identification Failures Confidence: HIGH p/secrets
Critical
SQL Injection — consulta construida por concatenación de string (taint cross-file)
📄 app/api/nutrition/search.py · línea 83 · trailofbits · python.sqli.taint-sql-injection
Datos controlados por el usuario (food_name del query param HTTP) fluyen sin sanitizar hasta una consulta SQL construida por concatenación. Detectado con análisis cross-file de Semgrep Pro: el input entra en routes.py:31 y se usa en search.py:83. Permite exfiltración completa de la base de datos de pacientes.
# app/api/nutrition/routes.py:31 — FUENTE (input HTTP)
31async def search_food(food_name: str = Query(...)): # ← user-controlled
32 return await FoodService.search(food_name)

# app/api/nutrition/search.py:83 — SUMIDERO (SQL injection)
81async def search(query: str):
82 sql = f"SELECT * FROM foods WHERE name LIKE '%{query}%'"
83 result = await db.execute(text(sql))
Fix: Usa parámetros vinculados: db.execute(text("SELECT * FROM foods WHERE name LIKE :q"), {"q": f"%{query}%"}). Nunca interpoles input de usuario en cadenas SQL.
CWE-89: SQL Injection OWASP A03:2021 — Injection Confidence: HIGH · Cross-file (Pro) trailofbits
Critical
JWT firmado con SECRET_KEY de valor fijo "change-me-in-production"
📄 app/core/security.py · línea 12 · p/security-audit · weak-jwt-secret
El secreto JWT usado para firmar todos los tokens de autenticación es un valor por defecto trivial. Un atacante puede falsificar tokens válidos para cualquier usuario, incluyendo administradores clínicos con acceso a datos de pacientes.
10from app.core.config import settings
11
12SECRET_KEY = os.environ.get("SECRET_KEY", "change-me-in-production")
13ALGORITHM = "HS256"
14ACCESS_TOKEN_EXPIRE_MINUTES = 43200 # 30 días — también problemático
Fix: Elimina el fallback. Usa SECRET_KEY = os.environ["SECRET_KEY"] (falla al arrancar si no está definida) y genera un secreto de 256 bits: openssl rand -hex 32. Reduce el TTL a 15-60 minutos.
CWE-1391: Weak Authentication OWASP A07:2021 — Auth Failures Confidence: HIGH p/security-audit
High
SSRF — URL controlada por usuario pasada directamente a httpx.get()
📄 app/services/nutrition_data.py · línea 156 · elttam · python.ssrf.requests-ssrf
El endpoint de importación de recetas acepta una URL de fuente de datos nutricionales proporcionada por el usuario y la usa directamente para una petición HTTP. Permite a un atacante escanear la red interna AWS, acceder al metadata service (169.254.169.254) y potencialmente comprometer otros servicios internos.
153async def import_from_url(source_url: str): # ← user input
154 # TODO: validar URL antes de producción
155 async with httpx.AsyncClient() as client:
156 response = await client.get(source_url, timeout=30)
Fix: Valida la URL contra una allowlist de dominios externos permitidos. Usa ipaddress para bloquear rangos privados (10.x, 172.16.x, 192.168.x, 169.254.x). Considera un proxy dedicado para peticiones externas.
CWE-918: SSRF OWASP A10:2021 — SSRF Confidence: HIGH elttam
High
Terraform — S3 bucket de datos clínicos sin cifrado en reposo
📄 infra/storage/s3.tf · línea 8 · p/terraform · aws-s3-no-server-side-encryption
El bucket S3 que almacena datos nutricionales y documentos de pacientes (RGPD - datos de salud) no tiene cifrado SSE-KMS configurado. Incumple el Reglamento General de Protección de Datos (Art. 32) y puede acarrear multas de hasta 20M€ o 4% del volumen global de negocio.
1 resource "aws_s3_bucket" "patient_data" {
2 bucket = "nutriflow-patient-data-prod"
3 # acl = "private" — deprecated, sin Block Public Access config
4 }
5
6 # ← FALTA aws_s3_bucket_server_side_encryption_configuration
7 # ← FALTA aws_s3_bucket_public_access_block
Fix: Añade aws_s3_bucket_server_side_encryption_configuration con SSE-KMS y una CMK dedicada. Añade también aws_s3_bucket_public_access_block con todos los flags a true.
CWE-311: Missing Encryption OWASP A02:2021 — Cryptographic Failures Confidence: HIGH RGPD Art.32 p/terraform
High
Dockerfile — imagen base FROM python:3.11 sin versión pinned + RUN as root
📄 docker/api/Dockerfile · línea 1, 24 · p/dockerfile · avoid-latest-tag · run-as-root
El Dockerfile usa python:3.11 sin digest pinned (cualquier actualización puede introducir vulnerabilidades silenciosamente) y ejecuta la aplicación como root (USER no está definido). Un RCE en la app daría control root sobre el contenedor.
1 FROM python:3.11 # sin digest SHA256 ni tag inmutable
2 WORKDIR /app
3 COPY requirements.txt .
23 RUN pip install -r requirements.txt
24 CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"] # ejecuta como root
Fix: Usa FROM python:3.11.9-slim@sha256:... con digest pinned. Añade antes del CMD: RUN useradd -m appuser && USER appuser.
CWE-250: Excessive Privilege OWASP A06:2021 — Vulnerable Components Confidence: MEDIUM p/dockerfile
Medium
MD5 usado para hash de passwords de usuarios clínicos
📄 app/models/user.py · línea 67 · p/security-audit · hashlib-insecure-hash-function
Las contraseñas se almacenan como MD5 sin salt, un algoritmo roto desde 1996. Una brecha de base de datos expondría todas las contraseñas en minutos mediante tablas rainbow. Los usuarios son dietistas con acceso a datos médicos de pacientes.
65 def set_password(self, raw_password: str):
66 import hashlib
67 self.password_hash = hashlib.md5(raw_password.encode()).hexdigest()
Fix: Usa passlib[bcrypt] con factor de coste 12: pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto").
CWE-327: Broken Crypto Algorithm OWASP A02:2021 — Cryptographic Failures Confidence: HIGH p/security-audit
Resumen Ejecutivo — Acciones Inmediatas
Prioridad Acción Impacto Esfuerzo
1 Rotar credenciales AWS y moverlas a Secrets Manager / env vars Compromiso total de cuenta AWS + datos pacientes 30 min
2 Reemplazar consultas SQL dinámicas por parámetros vinculados Exfiltración completa de base de datos 2-4 h
3 Hardcode SECRET_KEY — eliminar fallback inseguro en JWT Falsificación de tokens de admin / acceso no autorizado 15 min
4 Añadir validación de URL y allowlist para import_from_url (SSRF) Acceso a metadata AWS + red interna 1-2 h
5 Cifrar bucket S3 con SSE-KMS (RGPD Art.32) Incumplimiento RGPD · multa hasta 20M€ 2-3 h
6 Migrar hashing de passwords de MD5 a bcrypt (coste 12) Exposición de passwords en brecha de datos 3-4 h
7 Pinnear imágenes Docker con digest SHA256 + usuario no-root Escalada de privilegios en contenedor comprometido 1 h
Archivos Generados
📁 static_analysis_semgrep_1/
📄 rulesets.txt 27 rulesets aprobados
📁 raw/
python-fastapi.sarif
python-security-audit.sarif
python-secrets.sarif
typescript-react.sarif
typescript-security-audit.sarif
docker-dockerfile.sarif
terraform-infra.sarif
trailofbits-python.sarif
trailofbits-js.sarif
elttam-python.sarif
apiiro-python.sarif
📁 results/
✓ results.sarif — 122 hallazgos consolidados
Métricas de Escaneo
Archivos analizados 5.203
Líneas de código 187.492
Reglas ejecutadas 4.318
Hallazgos pre-filtro 401
Hallazgos post-filtro (Important Only) 122
Reducción de ruido -70%
Tiempo total (paralelo × 5 Tasks) 4m 37s
Cruce de archivos (Pro) ✓ Activado
Telemetría enviada ✗ --metrics=off