A newer version of the Gradio SDK is available: 6.19.0
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
ValueErroroTypeError. - 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
StopAsyncIterationde 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 porquepytestno esta instalado.venv\Scripts\python.exe -m pytest -q: falla porquepytestno esta instalado en elvenv.- Se reprodujeron fallos con providers simulados:
- filtro de fuentes con
OPENALEXdevuelve 0 resultados, conopenalexdevuelve 1. year='s.f.'rompe el filtro de anos.- mezcla
year='2020'yyear=2019rompe el ordenamiento.
- filtro de fuentes con
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.pybackend/tools/search_engine.pybackend/providers/sources.pyconfig.pymodules/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.pydefine IDs en mayusculas.backend/providers/sources.pydefine grupos reales usados porsearch_engine.config.pydefine otro mapa mas amplio con fuentes que no estan implementadas.modules/research_tab.pydefineALL_SOURCESmanualmente.
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.pysi no es la fuente real.
Criterio de aceptacion:
- Activar
OpenAlexen UI guardaopenalex. - Desactivar
openalexrealmente evita llamadas a OpenAlex. - Seleccionar
allexpande 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.pybackend/providers/scopus.pybackend/providers/core_.pybackend/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:
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_keyycallable. search_engine.search()debe resolver la clave por provider y pasarla comoapi_key.- Si falta una clave requerida, devolver metadata tipo
sourceStatusen lugar de silencio.
Criterio de aceptacion:
- Con
SCOPUS_API_KEYen.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'lanzaValueError.year='2020'yyear=2019sin filtro lanzaTypeErroral 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.pymodules/research_tab.pymodules/graph_module.pyassets/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 solohttpyhttps. - Construir atributos JS con
json.dumps, no con reemplazos manuales. - Evitar
onclickinline; delegar eventos desde JS condata-*. - En grafo, usar
textContentpara texto y crear nodos DOM en vez de concatenar strings coninnerHTML. - 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 /Ttaskkillconshell=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_PASSWORDo crear usuario en un comando setup. - No mostrar credenciales en UI.
- Usar
passlibcon bcrypt/argon2 owerkzeug.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
.envcuidadosamente 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.pymodules/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:
class PipelineStopped(Exception):
pass
- Levantar
PipelineStopped. - Capturar
PipelineStoppeden 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:
update = gr.update(choices=models, value=models[0])
return update, update, update
o una lista de tres updates.
Criterio de aceptacion:
- Cambiar de
mistralagroqactualiza los tres dropdowns.
10. Descarga de PDFs: SSRF, TLS deshabilitado y sin limite de tamano
Severidad: P0/P1
Archivos:
backend/tools/pdf_tools.pybackend/tools/pdf_processor.pymodules/pdf_tab.pymodules/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-Lengthy 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.mdconfig.pymodules/config/sources_config_tab.pybackend/providers/sources.pybackend/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_mlpueden 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/*.pybackend/providers/base.pybackend/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:
{
"source": "openalex",
"ok": true,
"results": [],
"error": None,
"status": "ok"
}
search()debe conservarsourceErrorsy 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
StopAsyncIterationporPipelineStopped.
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_tabygraph_module. - Validar links antes de renderizar.
- Quitar
onclickinline donde sea posible. - No devolver API keys al frontend.
- Remover credenciales por defecto o exigir password por env.
- Eliminar controles
taskkillglobales.
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.pyychat_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
SearchResultySourceSearchOutcome. - 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
pytestarequirements-dev.txtorequirements.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 -qcorre sin depender de red.- Los bugs reproducidos quedan cubiertos.
Orden de implementacion sugerido
registry.pyde fuentes/providers.- Normalizacion de resultados y anos.
- API keys por provider.
- Correccion de
update_models()yPipelineStopped. - Escape HTML y validacion de URLs.
- Retiro de
taskkill, credenciales por defecto y exposicion de secrets. - Downloader PDF seguro.
- Tests.
Archivos mas importantes para tocar
backend/tools/search_engine.pybackend/providers/sources.pybackend/providers/registry.pynuevomodules/config/sources_config_tab.pymodules/research_tab.pymodules/search_tab.pymodules/graph_module.pymodules/config/ai_tab.pybackend/tools/pdf_tools.pybackend/tools/pdf_processor.pymodules/chat_tab.pyapp.pyrequirements.txtorequirements-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.