🔬

Graph Evolution Report — NutriPaw API

GRAPH_EVOLUTION_nutripaw_v2.3.1_v2.4.0.md
Proyecto NutriPaw SaaS
Before v2.3.1 · 2026-05-28
After v2.4.0 · 2026-06-10
Lenguaje Python 3.11
Fecha análisis 2026-06-16
Trailmark Graph Evolution Skill: evolucion-grafo-codigo
CULTIVA IA — Seguridad
Nodos totales
Before: 248
271
+23
Funciones
Before: 187
209
+22
Aristas (calls)
Before: 412
459
+47
Entrypoints
Before: 14
17
+3
Nodos tainted
Before: 31
49
+18
2 CRITICAL
3 HIGH
4 MEDIUM
5 LOW
6 INFO
🚨 Cambios Estructurales Críticos
Critical Nueva ruta taint: entrada HTTP no validada alcanza execute_query()
Qué cambió
La función parse_recommendation_payload() (añadida en v2.4.0) invoca directamente db.execute_query() sin pasar por sanitize_input(). La arista parse_recommendation_payload → execute_query no existía antes. parse_recommendation_payload es ahora parte del subgrafo tainted y hereda el blast radius de execute_query (52 nodos downstream).
Impacto de seguridad
Entrada HTTP sin sanitizar puede alcanzar la capa de base de datos, creando riesgo de SQL injection. El endpoint /api/v2/recommendations es untrusted_external y su nueva arista al store salta el boundary de privilegio api → db.
+ edge: parse_recommendation_payload → execute_query (calls, certain)
! node: execute_query — high_blast_radius: 52 downstream
+ node: parse_recommendation_payload → tainted subgraph
+ entry: /api/v2/recommendations trust=untrusted_external CC=14
diff: sanitize_input() NOT called before execute_query()
Recomendaciones
  1. Añadir llamada a sanitize_input() antes de execute_query() en parse_recommendation_payload.
  2. Sustituir query dinámica por prepared statements / ORM parametrizado.
  3. Escribir fuzz harness orientado a parse_recommendation_payload (CC=14, candidato genotoxic).
  4. Añadir test de integración con payload malicioso (SQLi, NoSQLi).
Critical Eliminación de frontera de autorización en validate_pet_profile()
Qué cambió
La refactorización de validate_pet_profile() eliminó la llamada a check_ownership(user_id, pet_id). La arista validate_pet_profile → check_ownership desapareció del grafo. check_ownership estaba anotada como privilege_boundary; su desaparición del call graph levanta la barrera de autorización.
Impacto de seguridad
Un usuario autenticado puede solicitar recomendaciones para perfiles de mascotas que no le pertenecen (Insecure Direct Object Reference / IDOR). La eliminación del boundary api → ownership_check no quedó reflejada en el diff de texto porque la función validate_pet_profile sigue existiendo.
- edge: validate_pet_profile → check_ownership (calls, certain)
- node: check_ownership ← privilege_boundary subgraph (removed from graph)
! node: validate_pet_profile — CC before=4 CC after=9 Δ=+5
context: check_ownership was only called from validate_pet_profile
Recomendaciones
  1. Restablecer llamada a check_ownership() dentro de validate_pet_profile() o su caller directo.
  2. Verificar en revisión de código si la lógica se movió a otra capa (middleware, decorator).
  3. Añadir test de autorización: usuario B no puede acceder a perfil de usuario A.
⚠️ Hallazgos de Alta Severidad
High Nuevo entrypoint público con blast radius alto: /api/v2/batch-recommendations
+ entry: /api/v2/batch-recommendations trust=untrusted_external
! node: process_batch() — high_blast_radius: 38 downstream CC=11
note: rate-limiting decorator absent in after snapshot
  1. Aplicar rate-limiting y autenticación JWT al endpoint batch antes de producción.
  2. Limitar tamaño máximo del payload batch (max_items).
High process_ai_request() — incremento CC +12 en nodo tainted
! node: process_ai_request — CC before=6 CC after=18 Δ=+12
! node: process_ai_request ∈ tainted subgraph
+ 9 new branches (error paths, AI provider fallbacks)
  1. Refactorizar process_ai_request en sub-funciones (CC objetivo < 10).
  2. Candidato prioritario para harness genotoxic (CC > 10 + tainted).
High Nueva arista trust-boundary-crossing: worker Celery accede a secrets store sin intermediario
+ edge: fetch_ai_credentials() → vault_client.read_secret() (calls, certain)
! note: fetch_ai_credentials reachable from untrusted task payload
before: secrets accessed only through AuthService.get_token()
  1. Aislar acceso al secrets store detrás de AuthService (no acceso directo desde workers).
  2. Revisar si el task payload puede influir en el path del secreto leído.
🎯 Evolución de la Superficie de Ataque
NodoTipoTrust LevelArchivoDelta
POST /api/v2/recommendations REST endpoint untrusted_external api/v2/recommendations.py:48 NUEVO
POST /api/v2/batch-recommendations REST endpoint untrusted_external api/v2/batch.py:22 NUEVO
celery.task: ai_recommendation_task Async task untrusted_external workers/ai_worker.py:15 NUEVO
GET /api/v1/pets/profile (legacy) REST endpoint trusted_internal api/v1/pets.py:31 ELIMINADO
☣️ Cambios en Propagación de Taint
NodoTipoTainted víaArchivo
parse_recommendation_payloadfunction POST /api/v2/recommendations → parse_recommendation_payload api/v2/recommendations.py:72
process_ai_requestfunction celery.task: ai_recommendation_task → process_ai_request workers/ai_worker.py:54
build_promptfunction process_ai_request → build_prompt workers/ai_worker.py:89
validate_pet_profilefunction POST /api/v2/recommendations → validate_pet_profile domain/pets.py:103
fetch_ai_credentialsfunction process_ai_request → fetch_ai_credentials workers/ai_worker.py:112
💥 Cambios en Blast Radius
NodoTipoDownstream antesDownstream despuésDeltaArchivo
process_ai_requestfunction 831 +23 workers/ai_worker.py:54
AIRecommendationServiceclass 018 +18 (nuevo) services/ai_service.py:12
validate_pet_profilefunction 514 +9 domain/pets.py:103
LegacyProfileSerializerclass 123 -9 serializers/legacy.py:7
📈 Evolución de Complejidad (CC delta > 0)
NodoCC antesCC despuésDeltaEn taintedArchivo
process_ai_request 618 +12 workers/ai_worker.py:54
validate_pet_profile 49 +5 domain/pets.py:103
parse_recommendation_payload 14 +14 (nuevo) api/v2/recommendations.py:72
build_prompt 27 +5 workers/ai_worker.py:89
NutritionCalculator.compute 79 +2 domain/nutrition.py:45
Código Añadido (nodos nuevos, selección)
NodoTipoCCEntrypoint reachableArchivo
AIRecommendationServiceclass services/ai_service.py:12
parse_recommendation_payloadfunction14 api/v2/recommendations.py:72
process_batchfunction11 api/v2/batch.py:55
fetch_ai_credentialsfunction5 workers/ai_worker.py:112
HealthCheckSerializerclass serializers/health.py:3
Código Eliminado (nodos removidos)
NodoTipoCCEra privilege_boundaryArchivo (before)
check_ownershipfunction3 ✓ ← REVISIÓN REQUERIDA domain/authorization.py:88
LegacyProfileSerializerclass serializers/legacy.py:7
v1_recommendations_handlerfunction5 api/v1/recommendations.py:14
⚙️ Metodología
Herramienta: Trailmark graph-evolution (CULTIVA IA — Skill evolucion-grafo-codigo)
Before snapshot: v2.3.1 (git tag, 2026-05-28)
After snapshot: v2.4.0 (git tag, 2026-06-10)
Pre-análisis ejecutado: blast radius, taint propagation, privilege boundaries, entrypoint enumeration en ambos snapshots.
Diffs calculados: trailmark diff --json (nodos/aristas/entrypoints) + graph_diff.py (subgraph membership).
Contexto fuente: git diff v2.3.1..v2.4.0 para anotaciones de evidencia.
Limitaciones: El análisis es estático; rutas de ejecución condicionales en runtime pueden diferir. Las dependencias de terceros (openai SDK, celery) no se grafan en profundidad.