Novedades
Cada versión, con todos los cambios detallados. Las mejoras más recientes aparecen primero.
Notificaciones webhook y correcciones de logging
FlowCast ya puede avisarte cuando termina o falla cada etapa del pipeline. Configurás una URL (ntfy, Telegram, Slack, Discord o cualquier endpoint HTTP POST) y recibís un JSON con el detalle del evento. También se corrige un bug silencioso que suprimía los logs de startup después de que Alembic corría las migraciones.
- Nuevo Notificaciones webhook — cuatro eventos:
publish_success,download_error,render_error,publish_error. Cada notificación llega como JSON con nombre del podcast, episodio, URL de YouTube (en éxito) o mensaje de error (en fallo). Compatible con ntfy, Telegram bots, Slack, Discord o cualquier endpoint HTTP POST. - Nuevo Firma HMAC-SHA256 opcional — configurando
WEBHOOK_SECRET, cada request incluye el headerX-FlowCast-Signature: sha256=...para verificar que el payload no fue manipulado. - Nuevo Protección SSRF en el webhook — la URL destino se valida al arrancar la app y en cada envío. No es posible apuntar el webhook a IPs privadas o de loopback.
- Nuevo Modal cuando YouTube no está configurado — al hacer clic en "Conectar con YouTube" sin las credenciales de Google en
.env, ahora aparece un modal con instrucciones claras en lugar de un JSON crudo. Redirige de vuelta a la página de origen sin ensuciar la URL. - Nuevo Aviso YouTube desconectado en sidebar — indicador visible en el footer del sidebar cuando YouTube no está conectado, sin interrumpir la navegación.
- Fix Logs de startup suprimidos tras Alembic —
fileConfig()en el entorno de migraciones deshabilitaba silenciosamente todos los loggers del proceso, incluyendo los de la propia app. Los mensajesFlowCast startedyApplication startup completeya aparecen correctamente.
Imagen de portada del podcast desde el feed RSS
Al hacer poll de un feed RSS, FlowCast extrae ahora la imagen de portada del podcast y la muestra como thumbnail en el listado. Las migraciones de base de datos se volvieron más robustas para cubrir instalaciones existentes que nunca tuvieron historial Alembic.
- Nuevo Imagen de portada en el listado de podcasts — al hacer poll del feed RSS se extrae la URL de la portada (
<itunes:image>con fallback a<image>) y se muestra como thumbnail 80×80 en la tarjeta del podcast. La imagen pasa por el proxy/api/imgexistente, con las mismas protecciones SSRF de siempre. - Nuevo Migración Alembic 0002 — añade la columna
image_urla la tabla de podcasts. Se aplica automáticamente al arrancar; los datos existentes no se ven afectados. - Fix
init_db()— detección de instalaciones pre-Alembic — el arranque ahora distingue tres casos: instalación nueva (sin base de datos →create_all+ stamp head), instalación pre-v0.9.19 (base existente sin historial Alembic → stamp0001+upgrade head), e instalación Alembic-gestionada (upgrade head). Antes, instalar sobre una base sin historial Alembic podía saltar migraciones silenciosamente. - Fix Poll de RSS — transacción única — la imagen de portada y los episodios nuevos se persisten ahora en un solo
commit(), tanto en el scheduler automático como en el endpoint manual. Antes eran dos transacciones separadas. - Fix Botón "Volver al login" en la pantalla 2FA — los estilos inline del browser se reemplazaron por una clase del design system. El botón ahora respeta el color de texto muted y cambia correctamente con el tema claro/oscuro.
Cierre final auditoría Opus pre-v1.0 — todos los hallazgos resueltos
Último commit de seguridad antes de v1.0. Se cierra el hallazgo pendiente, y los dos restantes quedan documentados como aceptados con justificación. Score final auditoría Opus: ~95/100.
- Fix SVG eliminado del proxy de imágenes —
image/svg+xmlremovido de la allowlist de/api/img. Las portadas de podcasts son siempre JPEG/PNG/WebP; SVG no es necesario y podría ejecutar scripts si el browser lo renderiza como documento en lugar de como imagen. - Fix Auditoría Opus completamente documentada — los hallazgos restantes (
fontfilesin escapar y rate limit en endpoints autenticados) documentados como aceptados enSECURITY.mdcon justificación técnica. Todos los hallazgos de la auditoría tienen resolución explícita.
Auditoría Opus pre-v1.0 — 4 hardening de seguridad
Resultado de una auditoría exhaustiva pre-v1.0 con 16 categorías de análisis. Se cierran cuatro hallazgos; tres limitaciones arquitectónicas se aceptan y documentan con justificación.
- Fix Timing-safe login — la comparación de credenciales usa ahora
secrets.compare_digest()en lugar de==. Elimina el timing attack enPOST /login: un atacante ya no puede inferir caracteres correctos midiendo diferencias de tiempo en las respuestas. - Fix FFmpeg
%macros bloqueadas —escape_drawtext()reemplaza%por%%. Un título de episodio con%{pts}se renderizaba como el timestamp del frame en lugar del texto literal. - Fix
Path.is_relative_to()en borrado de archivos — reemplaza el anti-patternstr.startswith()para verificar que un archivo pertenece al directorio permitido antes de eliminarlo. La versión anterior podía confundir prefijos de nombre con prefijos de path. - Fix Rate limiting en proxy de imágenes —
GET /api/imgtiene ahora límite de 30 solicitudes/minuto por IP. Sin este límite, un usuario autenticado podía usar el proxy como port scanner lento.
Auditoría de seguridad: 100/100 — cierre de las últimas dos deducciones
Se cierran los dos últimos puntos abiertos de la auditoría de seguridad, alcanzando un score perfecto. Sin deducciones pendientes.
- Fix B-03 — Chunked body bypass cerrado — los endpoints de autenticación (
POST /login,POST /2fa) y preferencias (PATCH /api/preferences) ahora rechazan explícitamenteTransfer-Encoding: chunked. El check de tamaño de 2 KB no podía aplicarse sinContent-Length; este fix cierra el bypass. - Fix M-05 — DNS TOCTOU cerrado — la validación SSRF ahora ocurre en el momento exacto de la conexión TCP, no solo antes de llamar al cliente HTTP. Implementado con
_SSRFSafeTransport(httpx) para el descargador de MP3 y el proxy de imágenes, y con_SafeHTTPConnection/_SafeHTTPSConnection(urllib) para feedparser. Cierra el gap de DNS rebinding. - Nuevo 62 tests — 0 fallos — cobertura nueva para chunked rejection (5 tests) y validación SSRF en connect-time (5 tests).
Hardening pre-v1.0 — seguridad, robustez y migraciones automáticas
Quince mejoras distribuidas en cinco grupos: protecciones de seguridad adicionales, guards de concurrencia, migraciones automáticas de base de datos con Alembic, y tests de cobertura para los validadores de seguridad.
- Fix Rate limiting en mutaciones —
POST /api/episodes/{id}/download,/rendery/publishtienen ahora límite de 10 solicitudes/minuto por IP. Cierra deducción de auditoría. - Nuevo
TRUSTED_PROXY_IPS— nueva variable de entorno para configurar la IP del proxy inverso enProxyHeadersMiddleware. Permite hardening en despliegues donde la IP de Caddy/Traefik es conocida y fija. - Fix Límite de 20 MB en uploads de imagen — los endpoints de background y watermark de plantillas rechazan archivos mayores a 20 MB con doble verificación: rechazo anticipado por
Content-Lengthy verificación post-lectura. - Fix Guards de concurrencia en download y render — si ya hay una operación en curso para el episodio, la API responde 409 en lugar de lanzar un segundo proceso en paralelo. Consistente con el guard ya existente en publish.
- Nuevo Migraciones automáticas con Alembic —
database.pydetecta si la base de datos tiene historial Alembic: en instalaciones nuevas crea las tablas y establece el estado base; en instalaciones existentes aplica las migraciones pendientes. Elimina la necesidad de borrar la base de datos para cambios de schema. - Fix Validación de filtro
status—GET /api/episodes?status=Xdevuelve 400 para valores no reconocidos, en lugar de una lista vacía silenciosa. - Nuevo Tests de seguridad — 43 tests nuevos: SSRF validator (15 casos, incluyendo notación octal
0177.0.0.1), schema de plantillas (19 casos), y CSRF (9 casos). Cobertura de los validadores críticos de seguridad.
Correcciones de UI y compatibilidad de deploy
Cuatro correcciones menores: un fix crítico de compatibilidad que impedía arrancar la app en producción con versiones recientes de uvicorn, eliminación de un botón con enlace roto, y mejora visual del aviso de contacto de seguridad.
- Fix Compatibilidad uvicorn 0.34+ — la importación de
ProxyHeadersMiddlewarese hacía desdestarlette.middleware.proxy_headers, módulo que Starlette removió. Corregido auvicorn.middleware.proxy_headers. Sin este fix la app no arrancaba en producción. - Fix Botón "MP3 local disponible" eliminado — el botón en el detalle de episodio apuntaba a un endpoint que nunca existió, produciendo siempre un error 404.
- Fix Aviso de seguridad como dialog modal — el aviso de contacto de seguridad sin configurar usa ahora un
<dialog>HTML nativo que se abre al cargar el dashboard, en lugar de un banner con atributos de Bootstrap JS. - Fix SRI theme.css actualizado — hash
sha384actualizado en los tres templates tras la modificación detheme.css.
Correcciones de seguridad finales
Dos correcciones menores que cierran los últimos puntos abiertos de la auditoría: manejo de errores más preciso en el token OAuth de YouTube, y aviso visible cuando el contacto de seguridad no ha sido configurado.
- Fix Excepción amplia reducida — en la migración del token de YouTube,
except (InvalidToken, Exception)reemplazado porexcept InvalidToken. Solo se captura el error de descifrado esperado; otros errores de I/O ya no se silencian accidentalmente. - Nuevo Aviso de contacto de seguridad — si
SECURITY_CONTACTmantiene el valor por defecto, el dashboard muestra un banner dismissable recordando configurarlo para que/.well-known/security.txtsea válido.
Correcciones en documentación de seguridad
Versión de mantenimiento: correcciones en el documento de seguridad interno (SECURITY.md). Sin cambios en código ni en comportamiento de la aplicación.
- Fix Referencias de código actualizadas en la documentación de seguridad — líneas desplazadas por los cambios de las fases anteriores.
- Fix TTL del CSRF clarificado: 30 minutos efectivos (cookie) vs. 1 hora (token firmado).
- Fix Tabla de auditorías por fase reemplaza el score único desactualizado en la documentación.
Hardening de seguridad — Fase 3
Cuatro correcciones finales de la auditoría de seguridad: el endpoint de salud ya es accesible para monitoreo externo, la escritura del token de YouTube ya no tiene ventana de exposición de permisos, el cierre de sesión es ahora resistente a CSRF, y los assets CSS de las páginas de login y 2FA incluyen verificación de integridad.
- Fix /health accesible públicamente — el endpoint de salud ya no requiere sesión activa. Herramientas de monitoreo y el healthcheck de Docker funcionan correctamente. Rate limiting de 30/minuto activo.
- Fix Race condition en token de YouTube — el archivo del token OAuth ahora nace con permisos
0o600desde el primer byte, eliminando la ventana entre escritura ychmod. - Fix Logout resistente a CSRF — el cierre de sesión pasó de
GETaPOSTcon token CSRF. Ya no es posible cerrar la sesión del usuario con un enlace externo. - Fix SRI en login y 2FA — los CSS de las páginas de acceso incluyen ahora
integrity="sha384-...". El browser verifica la integridad de los assets antes de aplicarlos.
Hardening de seguridad — Fase 2
Cuatro correcciones de seguridad sin impacto visible en la interfaz: el rate limiting ahora funciona correctamente detrás de Caddy, la ventana de ataque de la sesión intermedia se redujo de 7 días a 5 minutos, las rutas internas del contenedor ya no se exponen en la API, y el header HSTS lo emite ahora la app directamente además del proxy.
- Fix Rate limiting real detrás de Caddy —
ProxyHeadersMiddlewarelee el headerX-Forwarded-Forpara obtener la IP real del cliente. Sin este fix, el límite de 5 intentos/minuto en el login era inoperante detrás del proxy. - Fix Sesión intermedia — TTL reducido — la cookie del paso entre contraseña y TOTP expira en 5 minutos en lugar de 7 días. Reduce la ventana de ataque si la cookie es interceptada antes de completar el 2FA.
- Fix Paths internos fuera de la API —
mp3_path,render_path,ffmpeg_cmdyffmpeg_logeliminados de las respuestas JSON. Las rutas del contenedor y los comandos FFmpeg ya no son accesibles vía API. - Fix HSTS en la app — el header
Strict-Transport-Security(2 años, includeSubDomains, preload) lo emite ahora la app directamente además de Caddy, cuandoAPP_BASE_URLes HTTPS.
Hardening de seguridad — Fase 1 + Tipografía personalizable de interfaz
Segunda auditoría de seguridad completada — cinco hallazgos críticos corregidos, incluyendo ejecución sin root en Docker, protección contra inyección en FFmpeg y verificación CSRF en el flujo OAuth de YouTube. Además, la interfaz ahora es personalizable: elegí la fuente, el tamaño y el grosor directamente desde Configuración.
- Fix Docker sin root — el contenedor corre como usuario
flowcast(UID 1001) en lugar de root.no-new-privilegesactivo en docker-compose. - Fix Protección FFmpeg injection — validators Pydantic bloquean colores malformados y expresiones posicionales con caracteres fuera de whitelist.
- Fix CSRF en OAuth de YouTube — el
statese verifica consecrets.compare_digesty se consume tras el callback exitoso. - Fix SSRF — tres gaps cerrados: bloqueo de IPv4-mapped IPv6 (
::ffff:x.x.x.x), CG-NAT100.64.0.0/10y notación octal antes del resolver del SO. - Fix SSRF en redirects — feedparser y el downloader de MP3 re-validan cada redirect con
validate_external_urlantes de seguirlo. - Nuevo Tipografía de interfaz personalizable — elegí entre Cantarell, Montserrat, Lato o Ubuntu desde Configuración. La preferencia se persiste en base de datos.
- Nuevo Tamaño de texto — cinco opciones (S, M, L, XL, XXL) que escalan toda la interfaz.
- Nuevo Grosor de texto — Normal o Negrita para las cuatro fuentes.
- Fix Cajas del picker de tipografía con dimensiones fijas idénticas — eliminado el crecimiento variable.
- Fix Botón "Guardar cambios" en Configuración unificado visualmente con el del editor de plantillas.
Tipografía en plantillas y mejoras visuales en el editor
El editor de plantillas estrena selector de tipografía y control de posición horizontal del título. Además corregimos varios detalles visuales: el color picker ahora muestra el color seleccionado, los valores px están alineados con los sliders y los iconos Phosphor ya no se desplazan respecto al texto.
- Nuevo Selector de tipografía — elige entre Liberation, Montserrat, Lato, Bebas Neue o Ubuntu para el título del episodio. Vista previa "Aa" en el editor y renderizado real con FFmpeg.
- Nuevo Posición X del título — slider 0–1920 px con botón "Centrar" que centra el texto automáticamente sin importar su longitud.
- Fix Color picker reemplazado por swatch circular — el cuadro blanco vacío ya no aparece; muestra el color activo correctamente.
- Fix Botón "Centrar" guardaba el píxel 960 fijo en lugar de la expresión de centrado dinámico — corregido.
- Fix Valores
pxde los sliders alineados verticalmente al centro del control (antes quedaban debajo en una línea separada). - Fix Iconos Phosphor alineados correctamente con el texto en listas y encabezados.
- Nuevo Guía de instalación en Easypanel y archivo
docker-compose.easypanel.ymlalternativo. - Fix Deploy en VPS: validación de
DOMAINal arrancar Caddy — el contenedor falla rápido con mensaje claro si la variable está vacía.
Instalación limpia — dominio desde .env
Corregimos el proceso de instalación en VPS: el dominio ahora se lee automáticamente desde el archivo .env y Caddy obtiene el certificado SSL sin errores. También limpiamos la documentación para nuevos usuarios.
- Fix El
Caddyfileusa{env.DOMAIN}— ya no hay que editar el archivo manualmente para configurar el dominio. - Fix El servicio
caddyendocker-compose.ymlrecibe el.envpara que la variableDOMAINresuelva correctamente. - Fix
.env.example: campoDOMAINagregado como requerido;RSS_FEED_URLeliminada (nunca fue usada); generación deSECRET_KEYusaopenssl rand -base64 32. - Fix README actualizado: eliminado paso de SSH key (repo público),
git cloneusa HTTPS, pasos renumerados.
Hardening de seguridad — auditoría completa
Aplicamos diez correcciones derivadas de una auditoría de seguridad externa realizada con dos agentes especializados — uno simulando un usuario externo buscando vulnerabilidades, otro actuando como experto en seguridad IT. Todos los hallazgos críticos y altos quedaron resueltos.
- Fix Rate limiting de 5 intentos/minuto agregado al paso de verificación 2FA, igual que en el login.
- Fix Race condition en la creación del secreto TOTP — el archivo ahora se crea con permisos restrictivos desde el primer byte, sin ventana de exposición.
- Fix TTL del token CSRF reducido de 60 a 30 minutos.
- Nuevo Subresource Integrity (SRI) — todos los assets CSS y JS incluyen hash
sha384para que el navegador detecte cualquier modificación. - Nuevo Variable de entorno
SECURITY_CONTACTpara configurar el email publicado en/.well-known/security.txt. - Nuevo
.dockerignore— el directoriodata/y el archivo.envya no se incluyen en el contexto de build de Docker. - Fix Historial git reescrito para eliminar referencias a herramientas de desarrollo internas.
Cantarell, Phosphor Icons y open source
FlowCast es ahora open source bajo la licencia PolyForm Noncommercial 1.0.0. Uso personal y educativo libre; uso comercial prohibido sin permiso. También estrena tipografía e iconografía nuevas, más alineadas con el diseño GNOME Adwaita.
- Nuevo Licencia PolyForm Noncommercial 1.0.0 — FlowCast es open source. El código está disponible en GitHub.
- Nuevo Aviso de licencia y autoría en la página de Configuración, con enlace a la licencia completa.
- Nuevo Tipografía migrada a Cantarell — la fuente oficial del entorno GNOME, servida localmente.
- Nuevo Iconografía migrada a Phosphor Icons 2.1.2, también servida localmente. Sin dependencias externas.
- Nuevo Controles tipo interruptor rediseñados con estilo AdwSwitch — el mismo aspecto que GtkSwitch en GNOME.
- Fix Los signos
¿y¡se renderizan correctamente en todos los navegadores y sistemas operativos. - Fix Icono inexistente en el dashboard reemplazado por el equivalente correcto de Phosphor Icons.
FlowCast, con mayúscula
La app ya tiene nombre oficial: FlowCast. Corregimos el nombre en todo el texto visible de la aplicación — login, sidebar, pantalla de verificación, documentación y logos. Los identificadores internos de código no cambiaron. También estrenamos el nuevo logo: el círculo con barras de audio reemplaza al diseño anterior en el sidebar, la pantalla de login y la verificación en dos pasos. Cambia automáticamente entre la versión clara y oscura según el tema activo.
- Nuevo Nuevo logo en sidebar, login y verificación 2FA — cambia automáticamente entre versión clara y oscura según el tema activo.
- Nuevo Nombre corregido a FlowCast en toda la interfaz visible.
- Fix Logo duplicado que aparecía en ciertas condiciones de carga.
- Fix Tamaño del logo en login y 2FA — ahora respeta las dimensiones correctas.
Mejoras visuales y documentación de seguridad
- Fix Alineación correcta de listas
ul/ol— el reset CSS borraba elpadding-leftdel navegador. - Nuevo Ventana de confirmación nativa (elemento
<dialog>) al desconectar YouTube, reemplazando elconfirm()del navegador. - Nuevo Creación de
SECURITY.mdcon documentación completa de los 19 controles de seguridad implementados. - Fix Corrección de espaciado en la sección de credenciales de la página de configuración.
Títulos de página alineados y diseño consistente
- Fix Altura mínima de
38pxen los encabezados de página para que los títulos queden al mismo nivel visual en todas las secciones. - Fix Actualización de cache busting a
?v=0.9.17en todos los assets CSS y JS.
Rediseño completo de la interfaz — Fase final
- Nuevo Sistema de diseño basado en GNOME Adwaita HIG: tokens CSS, tipografía Inter Variable, íconos Bootstrap Icons.
- Nuevo Sidebar fijo con toggle de tema claro/oscuro sincronizado con la preferencia del sistema (
prefers-color-scheme). - Nuevo Rediseño de todas las páginas: Dashboard, Episodios, Podcasts, Plantillas, Configuración, Login y TOTP.
- Nuevo Botones pill, cards con elevación y transiciones suaves entre temas.
- Fix Todos los event handlers movidos a
addEventListenerpara cumplir con la Content Security Policy (CSP con nonce).
Pipeline completo end-to-end
- Nuevo Pipeline automático: RSS → descarga MP3 → renderizado FFmpeg → publicación YouTube.
- Nuevo Autenticación en dos pasos: contraseña + TOTP (RFC 6238) con
pyotp. - Nuevo Token OAuth2 de YouTube cifrado con Fernet (AES-128-CBC + HMAC-SHA256).
- Nuevo Protección SSRF en todas las URLs externas (feeds, audio, proxy de imágenes).
- Nuevo Sanitización de HTML de feeds RSS con
nh3antes de guardar en base de datos. - Nuevo Rate limiting en
POST /login(5/min) yGET /health(30/min).