letxinet / ANALISIS_BUGS_REFACTORIZACION.md
C2MV's picture
Initial upload for Build Small Hackathon
68fb5e2 verified
|
Raw
History Blame Contribute Delete
17.1 kB
# Analisis de bugs y refactorizacion
Fecha de revision: 2026-06-08
## Resumen ejecutivo
El proyecto compila y la app importa correctamente, pero hay fallos funcionales y de seguridad que pueden hacer que la interfaz prometa capacidades que el backend no ejecuta, que algunas busquedas fallen silenciosamente, y que datos externos se rendericen como HTML sin escape.
Hallazgos mas importantes:
- La configuracion de fuentes esta desalineada: la UI usa IDs en mayusculas y el motor usa IDs en minusculas.
- Scopus, CORE y SerpAPI no reciben API keys aunque existan en `.env`.
- La normalizacion de anos puede romper busquedas con `ValueError` o `TypeError`.
- Hay varias rutas de XSS/HTML injection al insertar metadatos externos en `gr.HTML`.
- Los controles de sistema pueden matar todos los procesos `python.exe`.
- La app crea y muestra credenciales por defecto `admin/admin123`.
- Las API keys se devuelven al frontend en la pestana de modelos.
- El mecanismo de detener pipeline usa `StopAsyncIteration` de forma insegura para async generators.
- La refactorizacion prioritaria debe centralizar fuentes/providers, sanitizar salidas HTML y separar configuracion sensible del cliente.
## Verificaciones realizadas
- `python -m compileall -q .`: OK.
- `venv\Scripts\python.exe -c "import app; print('app import ok')"`: OK.
- `python -m pytest -q`: falla porque `pytest` no esta instalado.
- `venv\Scripts\python.exe -m pytest -q`: falla porque `pytest` no esta instalado en el `venv`.
- Se reprodujeron fallos con providers simulados:
- filtro de fuentes con `OPENALEX` devuelve 0 resultados, con `openalex` devuelve 1.
- `year='s.f.'` rompe el filtro de anos.
- mezcla `year='2020'` y `year=2019` rompe el ordenamiento.
## Severidad
- P0: riesgo de seguridad o accion destructiva.
- P1: rompe flujo principal o fuente importante.
- P2: inconsistencia funcional, deuda tecnica con alto costo futuro.
- P3: mejora de robustez, UX o mantenibilidad.
## Hallazgos detallados
### 1. Configuracion de fuentes inconsistente
Severidad: P1
Archivos:
- `modules/config/sources_config_tab.py`
- `backend/tools/search_engine.py`
- `backend/providers/sources.py`
- `config.py`
- `modules/research_tab.py`
Sintoma:
La pestana de configuracion guarda fuentes como `OPENALEX`, `SCOPUS`, `LA_REFERENCIA`, pero el motor de busqueda filtra contra `openalex`, `scopus`, `lareferencia`.
Causa raiz:
Hay varias fuentes de verdad:
- `modules/config/sources_config_tab.py` define IDs en mayusculas.
- `backend/providers/sources.py` define grupos reales usados por `search_engine`.
- `config.py` define otro mapa mas amplio con fuentes que no estan implementadas.
- `modules/research_tab.py` define `ALL_SOURCES` manualmente.
Ademas, en `sources_config_tab.py`, la linea que inicializa `_enabled_sources` dentro de `create_sources_config_tab()` no declara `global`, por lo que crea una variable local. Al arrancar, la configuracion queda ignorada. Cuando el usuario cambia un checkbox, `_update_enabled()` si usa `global`, pero guarda IDs en mayusculas y puede filtrar todo.
Impacto:
- La UI puede decir que una fuente esta activa, pero el backend no la usa.
- El usuario puede desactivar/activar fuentes y dejar la busqueda sin providers efectivos.
- Dificulta diagnosticar por que "no hay resultados".
Correccion recomendada:
- Crear un registro unico de fuentes, por ejemplo `backend/providers/registry.py`.
- Usar siempre IDs canonicos en minusculas.
- Hacer que la UI derive sus opciones desde ese registro.
- Normalizar aliases antes de guardar configuracion.
- Eliminar o consolidar `config.py` si no es la fuente real.
Criterio de aceptacion:
- Activar `OpenAlex` en UI guarda `openalex`.
- Desactivar `openalex` realmente evita llamadas a OpenAlex.
- Seleccionar `all` expande solo providers implementados o marca claramente los no implementados.
Pruebas minimas:
- `expand_sources(["all"])` solo devuelve IDs canonicos.
- `enabled_sources=["OPENALEX"]` se normaliza a `["openalex"]`.
- `enabled_sources=["openalex"]` permite resultados de OpenAlex.
### 2. Scopus, CORE y SerpAPI no reciben API keys
Severidad: P1
Archivos:
- `backend/tools/search_engine.py`
- `backend/providers/scopus.py`
- `backend/providers/core_.py`
- `backend/providers/serpapi.py`
Sintoma:
Los providers premium o con clave devuelven lista vacia si `api_key` no se pasa como argumento. El motor los llama asi:
```python
PROVIDERS[src](query, limit=min(max_results, 50))
```
Causa raiz:
Las claves estan en `.env`, pero no existe una capa que lea `SCOPUS_API_KEY`, `CORE_API_KEY` o `SERPAPI_API_KEY` y las inyecte al provider correspondiente.
Impacto:
- Scopus, CORE y SerpAPI aparecen configurables pero no funcionan.
- El sistema no distingue "sin resultados" de "no habia credencial".
Correccion recomendada:
- En el registro unico, cada provider debe declarar `env_key`, `requires_key` y `callable`.
- `search_engine.search()` debe resolver la clave por provider y pasarla como `api_key`.
- Si falta una clave requerida, devolver metadata tipo `sourceStatus` en lugar de silencio.
Criterio de aceptacion:
- Con `SCOPUS_API_KEY` en `.env`, `search(..., sources=["scopus"])` llama a Scopus con la clave.
- Sin clave, el resultado indica `scopus: missing_api_key`.
### 3. Filtro y ordenamiento por ano rompen con datos heterogeneos
Severidad: P1
Archivo:
- `backend/tools/search_engine.py`
Sintoma:
El filtro usa `int(r.get("year", 0))` sin validar. Si un provider devuelve `s.f.`, `N/A`, una fecha completa o un string no numerico, la busqueda completa falla.
Reproduccion:
- `year='s.f.'` + `year_start='2020'` lanza `ValueError`.
- `year='2020'` y `year=2019` sin filtro lanza `TypeError` al ordenar.
Causa raiz:
No hay normalizacion de metadatos a la entrada del motor.
Correccion recomendada:
- Crear `parse_year(value) -> Optional[int]`.
- Normalizar todos los resultados inmediatamente despues de recibirlos.
- Ordenar con una key que siempre devuelva int: `parse_year(x.get("year")) or 0`.
- Los filtros deben ignorar o conservar documentos sin ano segun politica explicita.
Criterio de aceptacion:
- `year="2020"`, `year=2020`, `year="2020-05-01"` se tratan como 2020.
- `year="s.f."`, `None`, `"N/A"` no rompen.
### 4. HTML injection / XSS en resultados, referencias y grafo
Severidad: P0
Archivos:
- `modules/search_tab.py`
- `modules/research_tab.py`
- `modules/graph_module.py`
- `assets/custom.js`
Sintoma:
Campos de proveedores externos se insertan directo en HTML:
- titulo
- autores
- abstract
- DOI
- PDF URL
- fuente
- contenido generado por IA
Tambien se usan `onclick` inline y `innerHTML`.
Impacto:
Un resultado academico malicioso o un dato corrupto puede inyectar HTML/JS en la app. Aunque sea local, el riesgo aumenta si se comparte con usuarios, se usa `share=True`, o se abre en red.
Correccion recomendada:
- Escapar texto con `html.escape`.
- Validar URLs con `urllib.parse`; permitir solo `http` y `https`.
- Construir atributos JS con `json.dumps`, no con reemplazos manuales.
- Evitar `onclick` inline; delegar eventos desde JS con `data-*`.
- En grafo, usar `textContent` para texto y crear nodos DOM en vez de concatenar strings con `innerHTML`.
- Revisar salida Markdown a HTML; si se usa `markdown`, sanitizar o restringir tags.
Criterio de aceptacion:
- Un titulo como `<img src=x onerror=alert(1)>` se muestra como texto, no ejecuta codigo.
- Una URL `javascript:alert(1)` no se renderiza como link.
### 5. Controles destructivos: reiniciar y matar procesos
Severidad: P0
Archivo:
- `app.py`
Sintoma:
Los botones de control ejecutan:
- `taskkill /F /IM python.exe /T`
- `taskkill` con `shell=True`
Impacto:
- Puede matar la app actual.
- Puede matar otros procesos Python del usuario.
- Puede interrumpir trabajos no relacionados.
Correccion recomendada:
- Eliminar el boton "Matar Procesos" de la UI normal.
- Si se requiere restart, reiniciar solo el proceso actual con un supervisor controlado.
- Evitar `shell=True`.
- No matar por nombre de proceso global.
Criterio de aceptacion:
- Ningun boton de UI mata todos los `python.exe`.
- Reinicio, si existe, afecta solo a la instancia actual.
### 6. Credenciales por defecto y hash debil
Severidad: P0/P1 segun despliegue
Archivo:
- `app.py`
Sintoma:
La app crea `admin/admin123` y lo muestra en el mensaje de login. La contrasena se almacena con SHA-256 simple, sin sal ni factor de coste.
Impacto:
- Cualquier usuario que vea el login sabe la credencial.
- Si se filtra la base, el hash es barato de romper.
Correccion recomendada:
- Exigir `LETXIPU_ADMIN_PASSWORD` o crear usuario en un comando setup.
- No mostrar credenciales en UI.
- Usar `passlib` con bcrypt/argon2 o `werkzeug.security`.
- Forzar cambio de password inicial.
Criterio de aceptacion:
- Sin password configurado, la app no crea admin debil.
- El mensaje de login no contiene credenciales.
### 7. API keys expuestas al frontend
Severidad: P0/P1
Archivo:
- `modules/config/ai_tab.py`
Sintoma:
La pestana de modelos precarga `MISTRAL_API_KEY` en un textbox y al cambiar provider devuelve la clave al cliente.
Impacto:
- Cualquier persona autenticada puede inspeccionar el HTML/estado y extraer secretos.
- Aumenta el riesgo si la app se comparte.
Correccion recomendada:
- Mostrar solo estado: configurada/no configurada.
- Si se permite actualizar claves, hacerlo con un flujo de escritura al `.env` cuidadosamente autorizado.
- Nunca devolver claves existentes al navegador.
Criterio de aceptacion:
- El frontend no recibe valores completos de API keys.
- El endpoint/evento solo devuelve mascara, por ejemplo `sk-...abcd`.
### 8. Detener pipeline puede terminar como error generico
Severidad: P1/P2
Archivos:
- `backend/pipeline.py`
- `modules/research_tab.py`
Sintoma:
`_checkpoint()` levanta `StopAsyncIteration`. En async generators, Python convierte esto en `RuntimeError: async generator raised StopAsyncIteration`.
Impacto:
- El boton detener puede mostrar error generico en vez de estado "detenido".
- La limpieza final puede no ejecutarse como se esperaba.
Correccion recomendada:
- Crear excepcion propia:
```python
class PipelineStopped(Exception):
pass
```
- Levantar `PipelineStopped`.
- Capturar `PipelineStopped` en handlers.
Criterio de aceptacion:
- Pulsar detener muestra estado detenido y no un traceback/error generico.
### 9. Cambio de proveedor IA en Research devuelve forma incorrecta
Severidad: P2
Archivo:
- `modules/research_tab.py`
Sintoma:
`update_models()` devuelve un solo `gr.update`, pero esta conectado a tres outputs: busqueda, sintesis y traduccion.
Impacto:
- Al cambiar proveedor, Gradio puede fallar o actualizar solo un componente.
Correccion recomendada:
- Devolver tres updates:
```python
update = gr.update(choices=models, value=models[0])
return update, update, update
```
o una lista de tres updates.
Criterio de aceptacion:
- Cambiar de `mistral` a `groq` actualiza los tres dropdowns.
### 10. Descarga de PDFs: SSRF, TLS deshabilitado y sin limite de tamano
Severidad: P0/P1
Archivos:
- `backend/tools/pdf_tools.py`
- `backend/tools/pdf_processor.py`
- `modules/pdf_tab.py`
- `modules/chat_tab.py`
Sintoma:
Se descargan URLs ingresadas por el usuario desde el servidor. Algunas descargas usan `verify=False`. No hay limite de tamano ni allowlist/bloqueo de IPs internas.
Impacto:
- SSRF contra servicios internos si la app se expone.
- Descarga de archivos enormes que agotan memoria o disco.
- TLS sin verificar permite MITM.
Correccion recomendada:
- Activar verificacion TLS.
- Bloquear IPs privadas/locales: `127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, link-local, metadata cloud.
- Limitar tamano por `Content-Length` y streaming con max bytes.
- Permitir solo `http`/`https`.
- Reusar un downloader comun.
Criterio de aceptacion:
- URL `http://127.0.0.1/...` se rechaza.
- PDF mayor al limite se corta con error claro.
### 11. Catalogo de fuentes promete providers no implementados
Severidad: P2
Archivos:
- `README.md`
- `config.py`
- `modules/config/sources_config_tab.py`
- `backend/providers/sources.py`
- `backend/tools/search_engine.py`
Sintoma:
Se mencionan fuentes como SciELO, CONAHCyT, UNAM, ANID, OasisBR, SNRD, MinCiencias, OpenReview, PapersWithCode o HuggingFace, pero el mapa real `PROVIDERS` no contiene implementaciones para muchas de ellas.
Impacto:
- La UI genera expectativas falsas.
- Los grupos `all`, `latam`, `ai_ml` pueden incluir fuentes que no hacen nada.
Correccion recomendada:
- El registro unico debe marcar `implemented=True/False`.
- La UI debe ocultar fuentes no implementadas o mostrarlas como "proximamente".
- Los grupos operativos deben incluir solo implementadas.
Criterio de aceptacion:
- No se puede seleccionar una fuente no implementada como si estuviera activa.
### 12. Manejo de errores silencioso en providers
Severidad: P2
Archivos:
- `backend/providers/*.py`
- `backend/providers/base.py`
- `backend/tools/search_engine.py`
Sintoma:
Muchos providers hacen `except Exception: return []`. `fetch_json()` tambien convierte cualquier error en `{"error": str(e)}`.
Impacto:
- Timeouts, credenciales faltantes, 403/429 y errores de parseo se ven igual que "0 resultados".
- Dificulta depurar fuentes rotas.
Correccion recomendada:
- Devolver estructura por fuente:
```python
{
"source": "openalex",
"ok": true,
"results": [],
"error": None,
"status": "ok"
}
```
- `search()` debe conservar `sourceErrors` y mostrarlos en UI.
Criterio de aceptacion:
- Si PubMed falla por timeout, la UI muestra "PubMed timeout" y no solo "sin resultados".
## Plan de refactorizacion recomendado
### Fase 1: estabilizacion funcional
Objetivo: que las busquedas basicas sean confiables.
Tareas:
- Crear `backend/providers/registry.py`.
- Consolidar grupos y aliases en un solo lugar.
- Normalizar IDs de fuentes a minusculas.
- Pasar API keys por provider desde `.env`.
- Agregar `parse_year()` y normalizacion de resultados.
- Corregir `update_models()` para multiples outputs.
- Reemplazar `StopAsyncIteration` por `PipelineStopped`.
Resultado esperado:
- `search()` no se cae por anos raros.
- Fuentes activadas en UI coinciden con providers usados.
- Scopus/CORE/SerpAPI usan claves si existen.
- Detener pipeline funciona sin error generico.
### Fase 2: seguridad de interfaz y secretos
Objetivo: eliminar riesgos P0.
Tareas:
- Escapar HTML en `search_tab`, `research_tab` y `graph_module`.
- Validar links antes de renderizar.
- Quitar `onclick` inline donde sea posible.
- No devolver API keys al frontend.
- Remover credenciales por defecto o exigir password por env.
- Eliminar controles `taskkill` globales.
Resultado esperado:
- Datos externos no ejecutan HTML/JS.
- Las claves no viajan al navegador.
- La app no mata procesos ajenos.
### Fase 3: downloader PDF seguro
Objetivo: robustecer lectura/vectorizacion/chat con PDFs.
Tareas:
- Crear `backend/tools/downloader.py`.
- Bloquear IPs internas y esquemas no permitidos.
- Descargar en streaming con limite de tamano.
- Activar TLS verification.
- Unificar `pdf_tools.py`, `pdf_processor.py` y `chat_tab.py`.
Resultado esperado:
- El PDF local funciona sin abrir SSRF ni OOM.
### Fase 4: arquitectura de resultados y errores
Objetivo: dejar de mezclar "sin resultados" con "fuente rota".
Tareas:
- Definir `SearchResult` y `SourceSearchOutcome`.
- Hacer que providers devuelvan resultados normalizados.
- Agregar `sourcesUsed`, `sourcesSkipped`, `sourceErrors`.
- Mostrar estado por fuente en UI.
Resultado esperado:
- La UI puede decir: "OpenAlex OK, PubMed timeout, Scopus falta API key".
### Fase 5: pruebas y CI local
Objetivo: evitar regresiones.
Tareas:
- Agregar `pytest` a `requirements-dev.txt` o `requirements.txt`.
- Tests unitarios para:
- expansion de fuentes
- normalizacion de aliases
- parseo de anos
- providers con/sin API key
- escape HTML
- detener pipeline
- Tests de integracion con providers simulados.
Resultado esperado:
- `pytest -q` corre sin depender de red.
- Los bugs reproducidos quedan cubiertos.
## Orden de implementacion sugerido
1. `registry.py` de fuentes/providers.
2. Normalizacion de resultados y anos.
3. API keys por provider.
4. Correccion de `update_models()` y `PipelineStopped`.
5. Escape HTML y validacion de URLs.
6. Retiro de `taskkill`, credenciales por defecto y exposicion de secrets.
7. Downloader PDF seguro.
8. Tests.
## Archivos mas importantes para tocar
- `backend/tools/search_engine.py`
- `backend/providers/sources.py`
- `backend/providers/registry.py` nuevo
- `modules/config/sources_config_tab.py`
- `modules/research_tab.py`
- `modules/search_tab.py`
- `modules/graph_module.py`
- `modules/config/ai_tab.py`
- `backend/tools/pdf_tools.py`
- `backend/tools/pdf_processor.py`
- `modules/chat_tab.py`
- `app.py`
- `requirements.txt` o `requirements-dev.txt`
## Nota sobre el estado del repo
El arbol de trabajo ya tenia cambios modificados y archivos sin seguimiento antes de este informe. No se debe hacer `reset` ni revertir esos cambios sin revisar su origen.