Auditoría OrganiLAB 1.4.3

Organizador Labstream · código en C:\Users\Munka\Claude\labstream-organizador · 15 sep 2026 · solo lectura, no se modificó nada.

Veredicto. La app cumple hoy su promesa de no borrar: en la bitácora real (raíz V:\Galeano, 1.154 movimientos, 6 lotes) no hay un solo error. Pero hay dos fallas que sí pueden destruir material de cliente por una ruta que la promesa no cubre: la conversión a 10 bits sobrescribe salidas cuando un clip existe en MP4 y MOV, y el renombrado silencioso con «(2)» rompe el emparejamiento de clips Sony con sus XML. Además, el servidor local acepta órdenes de cualquier página web abierta en el navegador, lo que permite mover archivos o instalar un ejecutable ajeno.

4Críticos
11Altos
15Medios
12Bajos

Qué arreglar primero

  1. Salidas de conversión a prueba de colisión. Conservar la extensión original en el nombre (clip.mov_10bit.mp4), rechazar encolar si otro trabajo ya reclama esa salida, y nunca hacer os.remove ni os.replace sobre un archivo existente. En «Reemplazar», volver a comprobar que la salida existe y usar un lote único por reemplazo.
  2. Cerrar el API local. Rechazar peticiones cuyo Host no sea 127.0.0.1:8765, exigir Content-Type: application/json y un token aleatorio por arranque que la interfaz envíe. Quitar ffmpeg, ffprobe, python_resolve y actualizaciones.url de lo que /api/config puede cambiar.
  3. Nunca renombrar por colisión. Convertir la colisión en aviso en el plan y en error en ejecución (saltar el archivo y registrarlo), en vez del sufijo «(2)». Reconocer el sidecar Sony C0001M01.XML en las reglas.
  4. Contención de rutas. En aplicar, stock a papelera, duplicados y respaldo, rechazar cualquier origen o destino que no cuelgue de la raíz. Corregir la comprobación de papelera, que aún busca «papelera» y no «_Para borrar».
  5. Actualizador seguro. Validar versión y nombre de archivo del manifiesto, exigir sha256 y HTTPS del mismo host, no actualizar con trabajos en curso, y hacer copia de la instalación antes del robocopy para poder volver atrás.

Seguridad de los datos

Crítico

MP4 y MOV del mismo clip comparten una sola salida de conversión

La salida se forma quitando la extensión y añadiendo _10bit.mp4. clip.mov y clip.mp4 producen el mismo destino; el segundo trabajo sobrescribe al primero, la verificación aprueba a ambos y «Reemplazar» manda los dos originales a _Para borrar/originales. Solo sobrevive un convertido. El puente con Resolve lo hace explícito con os.remove(dst). El asistente automatiza toda la cadena sin revisión humana.

organizador.py:1272-1278, 1289, 1373, 1487-1553 · resolve_bridge.py:240-241 · asistente.py:267-270

Alto

Colisiones de nombre se resuelven renombrando con «(2)»

Al fusionar grabaciones de varias tarjetas Sony en un mismo 00_Grabacion/SONY/, el segundo C0001.MP4 pasa a C0001 (2).MP4. Ese nombre ya no se reconoce como cámara y el siguiente plan lo propone mover a Exportados. Su XML queda huérfano y Resolve o Premiere no pueden reenlazar. No hay aviso en el plan.

organizador.py:868-875, 894, 692-750, 1541 · reglas.py:102

Alto

La carpeta «_Para borrar» se vuelve a planificar como basura en cada pasada

La comprobación sigue buscando el nombre viejo «papelera». Como el escaneo marca _Para borrar como basura, el plan propone moverla dentro de sí misma con confianza «alta» y marcada por defecto. Hoy falla con «Cannot move a directory into itself» y deja una carpeta parásita; cualquier cambio futuro en mover() lo convertiría en un automovimiento recursivo.

organizador.py:816, 827 · reglas.py:539-540

Alto

Archivos bloqueados dejan copias huérfanas sin registrar

Si un clip está abierto en Resolve, os.rename falla y el respaldo shutil.move copia el archivo pero no puede borrar el origen. La excepción sale antes de escribir la bitácora: origen intacto, copia de varios GB en destino, nada registrado. Reintentar crea copias «(2)».

organizador.py:895-899

Alto

Todos los reemplazos comparten el lote «optimizar»

Deshacer ese lote devuelve a su sitio todos los originales reemplazados en la historia de la app. «Reemplazar» tampoco vuelve a comprobar que el convertido siga existiendo: si se borró entre la verificación y el reemplazo, el original se va sin sustituto.

organizador.py:1538, 972-994

Alto

Destinos editables fuera de la raíz y movimientos entre unidades sin verificar

El campo de destino del plan acepta rutas absolutas y ..\.. sin comprobar contención. Entre unidades, shutil.move copia y borra sin comparar tamaño ni hash, y «Deshacer» solo usa os.rename, así que no puede revertirlo.

organizador.py:2104, 436-437, 878-900, 985

Medio

Deshacer omite entradas en silencio y mezcla lotes del mismo segundo

Si el destino ya no existe o el origen está ocupado, la entrada se salta sin avisar. El id de lote tiene resolución de segundo; la bitácora del proyecto ya muestra un lote con dos «fin». Las carpetas vaciadas se borran sin registro.

organizador.py:983, 907, 957-969

Medio

Aplicación parcial ante rutas largas

Una fila de 500 archivos se detiene en el primer fallo. Las rutas bajo _Para borrar/basura/ superan fácilmente 260 caracteres en NAS; no hay comprobación previa ni prefijo \\?\.

organizador.py:913-928

Medio

El puente con Resolve modifica y guarda el proyecto abierto del editor

Si no puede crear su propio proyecto, usa el que el editor tiene abierto: cambia resolución y fps, borra todos los trabajos de render en cola, añade bins y timelines, y guarda. Los temporales _render_* quedan dentro de la carpeta de cámara del cliente si algo falla o se cancela.

resolve_bridge.py:105-114, 155-159, 185, 260, 151, 250-255 · organizador.py:1440

Bajo

Masters dentro de una pieza son candidatos a conversión

Solo se excluye el contexto «Exportados». Un 01- Pieza/final.mp4 se ofrece para convertir y tras «Reemplazar» el master entregado acaba en _Para borrar/originales. «Apartar duplicados» sin selección aplica todos los grupos.

organizador.py:1257, 2166

Reglas de reconocimiento

Alto

El sidecar Sony C0001M01.XML no se reconoce

El patrón solo admite c0001.xml. El XML va a OTROS/ o a 01_Proyectos/Edicion mientras el MP4 va a SONY/. Sin embargo «Reemplazar» depende de ese nombre exacto para moverlo junto al clip.

reglas.py:101-102 · organizador.py:1541

Medio

BRAW nunca llega a ser candidato a conversión

ffprobe no lee BRAW, el análisis lo marca como error y los candidatos exigen análisis correcto. La rama BRAW es inalcanzable: toda la vía Resolve está muerta desde la interfaz.

organizador.py:1218-1226, 1241

Medio

Duplicados sin comprobar contenido

Se agrupan solo por nombre y tamaño, sin hash ni siquiera parcial. Clips de cámara a bitrate fijo con igual duración se declaran idénticos y uno se aparta. La pareja «versión» separa también un MOV intermedio de su MP4 master.

organizador.py:1697-1723

Medio

Carpetas «1» a «12» bajo un año se renombran como meses con confianza alta

Piezas numeradas sin título (2025/3) pasan a 03- Marzo marcadas por defecto.

reglas.py:62 · organizador.py:187-195

Bajo

Otros

Raíces de unidad (N:\) rompen las consultas por prefijo y dejan el mapa vacío. .dat e .idx se tratan como basura. Cualquier carpeta llamada «Cache» se clasifica como proxy. El año de clips Blackmagic se infiere de la fecha de modificación.

organizador.py:1066-1071, 1802 · reglas.py:156, 159, 268-277

Seguridad

Crítico

Cualquier página web puede dar órdenes al servidor local

Escucha solo en 127.0.0.1, pero no valida Host ni Origin, no pide token y parsea JSON sin mirar Content-Type. Un fetch en modo no-cors desde cualquier sitio abierto en el navegador pasa sin preflight. Con eso se puede reorganizar cualquier carpeta, mover rutas fuera de la raíz vía /api/stock/papelera o copiar el perfil entero a un recurso de red ajeno vía respaldo. /api/respaldo/plan es un GET que escribe config.

organizador.py:2459, 1916-1923, 1615-1629, 2025-2034, 2169-2177

Crítico

Dos cadenas de ejecución de código desde ese mismo agujero

Uno: /api/config acepta cualquier clave, incluida la ruta de ffprobe; un UNC de atacante se ejecuta al analizar. Dos: cambiar actualizaciones.url y llamar a /api/actualizacion/instalar descarga un zip ajeno, lo comprueba contra el sha256 del propio atacante y lo lanza como ejecutable.

organizador.py:2060-2067, 144-150, 1181-1185, 2113 · actualizador.py:47, 62, 115

Crítico

El actualizador confía en todo lo que dice el manifiesto

Sin firma. El sha256 se omite si el campo falta. La URL de descarga puede apuntar a otro host y a http. La cadena de versión se inserta sin validar en el .bat (inyección de comandos) y el nombre de archivo permite ..\ para escribir fuera de la carpeta de actualización.

actualizador.py:49-51, 62, 99, 114-116, 138, 144

Alto

La actualización mata la app con trabajos en curso y no puede volver atrás

os._exit a los 1,5 s salta la espera de conversión, respaldo y asistente. El bat solo espera a ese PID, no a otras instancias del exe. Robocopy sobrescribe archivo a archivo sin copia previa; un fallo a mitad deja exe viejo con _internal nuevo. El bat se escribe en cp1252 sin chcp, así que rutas con acentos fallan en consola OEM 850.

actualizador.py:159, 147-149, 142

Correcto: no hay shell=True, el handler de /static/ no permite traversal, enlaces simbólicos no se siguen, #recycle y @eadir se omiten, datos\ y ffmpeg\ quedan fuera del robocopy.

Interfaz

Alto

Estado viejo al cambiar de raíz

Cambiar la raíz solo limpia plan y candidatos. Con la tabla de stock de la raíz A en pantalla y la raíz B seleccionada, «Mover a _Para borrar» envía rutas de A con raíz B; el servidor no valida y relpath con .. saca el destino fuera de la papelera. «Copiar respaldo» sigue activo con el plan de otra raíz.

index.html:461, 478, 487 · organizador.py:1615-1629, 2171

Alto

Errores del servidor se tratan como éxito

api() alerta pero devuelve el objeto de error; los llamadores hacen .filter o .map sobre él y revientan. Si el servidor no responde JSON, la promesa muere sin manejar y botones como «Calcular plan» quedan deshabilitados para siempre.

index.html:419, 470, 481, 584-590, 614, 625

Alto

Doble clic duplica trabajos de conversión

«Encolar y arrancar» no se deshabilita y encolar() hace SELECT y luego INSERT sin bloqueo en hilos distintos. Igual con Verificar, Calcular respaldo y Stock a papelera.

index.html:625, 635, 487, 478 · organizador.py:1289-1295

Medio

Otros de interfaz

  • Ajustes se repinta desde el sondeo cada 2,5 s y descarta lo que se está escribiendo.
  • Claves de raíz sin normalizar: una raíz con barra final aparece «no escaneada» para siempre.
  • El plan pinta todas las filas sin límite y se repinta entero en cada tecla del filtro; con 25.000 archivos son MB de JSON.
  • Valores de perfil, croma y nombres de plantilla se pintan sin escapar; combinado con el CSRF es XSS persistente. Los nombres de archivo sí se escapan.
  • Doble escape en el título del asistente: «Brigard & Urrutia» se ve con &.
  • Modal sin rol, sin Escape ni foco; enlaces sin href no operables por teclado; contraste del ámbar por debajo de AA. Google Fonts cargado en una herramienta que puede estar sin red.

index.html:648-659, 463, 441, 585-601, 630, 655, 717, 414, 190-197, 8

Robustez y rendimiento

Alto

Hilos de fondo mueren en silencio y dejan banderas atascadas

analizar() no captura excepciones: un «database is locked» durante un escaneo grande deja el análisis en «corriendo» hasta reiniciar. bucle_cola() sin try/finally deja COLA.activa en true y el cierre de ventana nunca termina. copiar_respaldo escribe «corriendo» pero el cierre espera «copiando».

organizador.py:1211-1229, 1452-1474, 2129, 1833, 2486

Medio

Consultas que escanean la tabla entera

archivos_bajo() usa LIKE sobre índice binario y hace full scan; se llama una vez por carpeta no reconocida y una vez por cada elemento movido. Mover 12.000 autoguardados de After Effects son 12.000 barridos de 25.000 filas. Duplicados y Mapa hacen os.path.exists por SMB en cada archivo de más de 5 MB.

organizador.py:242-247, 265, 949, 1694, 1059

Bajo

Otros

  • El modo python_resolve apunta a un archivo que no se distribuye en el empaquetado.
  • El zip portátil lleva la lista completa de clientes, alias y mapa de unidades del estudio.
  • Los resolve_lote_*.json se acumulan en datos\; los bat generados llevan \r\r\n.
  • Endpoints muertos: /api/actualizacion/buscar, /api/duplicados GET, y modalTexto nunca conectado.
  • Sin pruebas automáticas; la prueba manual del README es anterior al cambio de nombre de la papelera, por eso se coló ese bug.

organizador.py:1400-1401, 2023, 2110 · empaquetar.py:47-57 · index.html:510

Método

Tres revisiones en paralelo sobre organizador.py, reglas.py, resolve_bridge.py, actualizador.py, empaquetar.py y static/index.html, con ejecución de las funciones de reglas contra casos límite y comprobación del contenido del zip de la app. Los hallazgos críticos se verificaron por segunda vez leyendo el código. Se revisó además la bitácora real de la copia portátil 1.4.1 en Descargas, que es la que se ha usado sobre el NAS.