flawopen.com/Teardowns/cve-2026-84973-jupyterlab-snapshot-checkout-toctou
CVE-2026-84973: TOCTOU en el flujo de snapshots de JupyterLab
Una comprobación de tiempo con precisión de un segundo podía permitir que el flujo de snapshots descargara y ejecutara un commit no aprobado de una solicitud de cambios con los permisos del trabajo. Actualice update-snapshots-checkout a la versión 1.0.0 o posterior.
Un revisor aprueba un paquete concreto, pero el mostrador de entrega solo comprueba el segundo del reloj. Si alguien cambia el paquete durante ese mismo segundo, el sustituto parece aprobado. La action comparaba marcas de tiempo con precisión de un segundo y aceptaba valores iguales, por lo que una actualización de la solicitud podía pasar junto al comentario del mantenedor.
Conceptos Clave y Términos
TOCTOU- Time-of-check to time-of-use: se toma una decisión de seguridad sobre un recurso que cambia antes de utilizarse.
SHA head de la solicitud de cambios- Identificador inmutable del commit situado en la punta de la rama de la solicitud. El flujo solo debe ejecutar el commit exacto autorizado.
Permisos del token de GitHub Actions- Operaciones que el GITHUB_TOKEN del trabajo puede realizar, como leer o escribir contenido y solicitudes de cambios.
CWE-367 / CWE-829- El advisory clasifica la carrera temporal como
CWE-367y la ejecución de contenido controlado por la solicitud comoCWE-829.
Mapeos de marcos
Análisis de Causa Raíz
Antes de la versión 1.0.0, update-snapshots-checkout obtenía pushed_at de la solicitud y created_at del comentario que activaba el flujo, convertía ambos valores a segundos Unix y rechazaba la ejecución solo cuando pushed_at era estrictamente mayor. Ambas marcas de tiempo de la API tienen precisión de un segundo. Por eso, una actualización y un comentario autorizado en el mismo segundo podían dar valores iguales y superar la comprobación. Después, la action guardaba el SHA actual y lo comprobaba tras el checkout; esa comparación solo demostraba que se descargó el SHA recién obtenido, no que el mantenedor lo hubiera autorizado. La corrección upstream cambia -gt por -ge y rechaza valores iguales o posteriores. El riesgo requiere una versión vulnerable y que pasos posteriores ejecuten contenido de la solicitud; el impacto depende de los permisos y credenciales del flujo.
Flujo de Ataque Paso a Paso
Un mantenedor de confianza activa el flujo
Se abre una solicitud de cambios y un OWNER, COLLABORATOR o MEMBER publica el comentario que activa el flujo de actualización de snapshots.
Una actualización controlada por el atacante coincide con el comentario
Un colaborador actualiza la rama en el mismo segundo. La API solo ofrece precisión de un segundo, por lo que los valores Unix pueden coincidir.
La comparación estricta acepta la igualdad
La action vulnerable rechaza únicamente cuando pushed_at es mayor que created_at. La igualdad no cumple esa condición y se registra el nuevo SHA de la rama.
El flujo descarga y puede ejecutar ese commit
El checkout coincide con el SHA recién registrado y supera la comprobación posterior. Si los pasos siguientes ejecutan actions o scripts de la solicitud, utilizan los permisos y credenciales del trabajo.
Actualice y reduzca los privilegios del flujo
Actualice a 1.0.0 o posterior. Fije las actions a SHAs inmutables, conceda al GITHUB_TOKEN solo permisos necesarios y mantenga el código de solicitudes no confiables lejos de secretos y trabajos privilegiados.
Código Fuente: Vulnerable vs. Seguro
Este es el cambio upstream de una línea: -ge rechaza la igualdad con la precisión temporal de la API. Cierra el bypass documentado del mismo segundo. Aun así, vincule la autorización a un SHA inmutable y reduzca permisos: el orden temporal no sustituye una autorización inmutable. El diff upstream conserva el mensaje de diagnóstico; dice «después», aunque ahora también se rechaza la igualdad.
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
echo "Updating is not allowed because the PR was pushed to (at $pushed_at) after the triggering comment was issued (at $COMMENT_AT)"
exit 1
fi
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualice todas las instancias de update-snapshots-checkout a 1.0.0 o posterior; busque también copias y forks.
- ✓Fije las actions a SHAs inmutables revisados y revise los cambios antes de actualizar esas referencias.
- ✓No autorice una rama móvil solo con marcas de tiempo; vincule la aprobación al SHA exacto y falle si cambia.
- ✓Defina permisos mínimos explícitos por trabajo y omita contents: write y pull-requests: write si no hacen falta.
- ✓Mantenga secretos y credenciales de despliegue fuera de trabajos que ejecutan código de solicitudes externas.
- ✓Añada pruebas de regresión que rechacen marcas de tiempo iguales y posteriores.
Fuentes
- Advisory de GitHub Security Lab GHSL-2026-203
- Advisory GHSA-wwhg-p79f-vfvm
- Commit de la corrección upstream 6a2505f
- MITRE CWE-367: carrera de tiempo de comprobación y uso
- MITRE CWE-829: inclusión de funcionalidad de una esfera de control no confiable
- Caso de workflow relacionado (distinto): Marimo GHSL-2026-226