BookinglyTech News
Software

Cuatro fallos reales descubiertos en la infraestructura de agentes de Celesto y CowAgent

Un análisis de código reveló cuatro defectos críticos, entre ellos un error de autorización que devolvía 500 y una fuga de archivos en la transcripción de voz.

2 min de lecturaDev.to0 vistas

Five pull requests, two merged upstream, and four real bugs in agent infrastructure. Aquí se detallan los hallazgos, las correcciones y la metodología que los detectó.

1. Header de autorización que devuelve 500

En Celesto, el middleware de autenticación usaba secrets.compare_digest con cadenas. Cuando el encabezado contenía caracteres no ASCII, el compare_digest lanzaba un TypeError, produciendo un error 500 en lugar de rechazar la petición. La solución fue comparar bytes:

if not secrets.compare_digest(supplied.encode(), expected.encode()):
    return JSONResponse(status_code=401, ...)

El problema era accesible sin autenticación: cualquier cliente con un encabezado de un solo byte generaba el fallo.

Celesto #614

2. Entrada de allowlist que normaliza a cadena vacía

La función InternetSettings.allowed_domains dividía el nombre de host por : pero no verificaba si el resultado estaba vacío. ::1 se convertía en "", lo que se resolvía como host local en socket.getaddrinfo y se inyectaba como wildcard en la lista de dominios permitidos. Se corrigió añadiendo una verificación explícita:

hostname = entry.split(":")[0]
if not hostname:
    raise ValueError(f"Could not extract hostname from: {entry!r}")

Celesto #615

3. Limiter que impide que el proceso salga

En CowAgent, el TokenBucket iniciaba su hilo de recarga sin daemon=True. Al no cerrarse, el proceso no terminaba porque threading._shutdown esperaba a que todos los hilos no daemon finalizasen. Además, un tpm fraccional provocaba división por cero y bloqueaba el generador de tokens.

La corrección consistió en hacer el hilo daemon, guardar el handle para close() y devolver temprano cuando rate <= 0.

CowAgent #3285

4. Fuga de descriptor en transcripción de voz

El método get_pcm_from_wav abría un archivo con wave.open y devolvía los frames sin cerrar el descriptor. Con 200 llamadas, se generaban 200 descriptores abiertos, agotándose los recursos.

La solución usó with para garantizar el cierre:

with wave.open(wav_path, "rb") as wav:
    return wav.readframes(wav.getnframes())

Lecciones y flujo de trabajo

El autor descubrió dos “bugs” que ya estaban corregidos upstream gracias a las pruebas existentes. Esto subraya la importancia de ejecutar el test suite antes de abrir PRs. La revisión manual, combinada con la prueba de código, logró detectar problemas que los test unitarios no cubrían.

El caso muestra que incluso en proyectos maduros, la combinación de análisis estático y pruebas puede revelar fallos críticos que afectarían a producción.