Actualidad

Ciberseguridad

Cuarentena de Cargo en Rust 1.100, una espera para el software

Rust 1.100 estabiliza la edad mínima de publicación en Cargo: retrasa versiones recién subidas a crates.io. Por defecto vale 0, así que hay que activarla.

Cuarentena de Cargo en Rust 1.100, una espera para el software
Cuarentena de Cargo en Rust 1.100, una espera para el software

A las 07:15 UTC del 20 de agosto de 2026, el equipo de seguridad de Rust recibió un aviso: la caja `proc-macro1`, recién subida a crates.io, descargaba una carga maliciosa desde su script de compilación. `arrayref`, una dependencia con años de historial, acababa de publicar una versión que dependía de ella y había retirado las anteriores. La versión envenenada aguantó 86 minutos en el registro. `internment` estuvo 90 y `append-only-vec`, 107. Ninguna llegó a las dos horas, pero cualquier `cargo update` lanzado en esa franja pudo compilar el código del atacante sin que el desarrollador tocara una línea.

Rust 1.100, que llega el 12 de noviembre, trae la respuesta del proyecto a ese patrón de ataque. Cargo podrá negarse a usar versiones demasiado jóvenes, con un ajuste llamado edad mínima de publicación y la clave `registry.global-min-publish-age`. El valor por defecto es `»0″`, así que actualizar el compilador no protege de nada por sí solo: alguien tiene que escribir una cifra en la configuración del proyecto. Hoy la función está en beta: el changelog de Cargo 1.100 la fecha el 12 de noviembre, cuando toca el canal estable.

Una ventana temporal no escanea código ni certifica que un paquete sea seguro. Compra horas para que los analizadores automáticos y los propios mantenedores miren una versión antes de que medio ecosistema la adopte. El precio es simétrico y aparece en el peor momento, porque el parche que corrige una vulnerabilidad también es una versión recién publicada.

Una cuenta ajena y dos horas de margen

El relato oficial está en el blog del equipo de respuesta de seguridad de Rust, firmado por Manish Goregaokar. El equipo no acusa al autor de `arrayref` de actuar con malicia, aunque sí sostiene que su ordenador o sus credenciales fueron comprometidos: la cuenta acabó publicando una versión que arrastraba al paquete malicioso y marcando como retiradas las versiones legítimas. Además de `proc-macro1`, se borraron `proc-macro-en`, `aovine`, `arone`, `aronenao` y `tinymember`, y la cuenta quedó bloqueada como medida de precaución.

Crate Versión Publicada (UTC) Retirada (UTC) Minutos online
`arrayref` 0.3.10 07:15:00 08:41:40 86
`internment` 0.8.7 07:34:07 09:04:11 90
`append-only-vec` 0.1.9 07:37:49 09:25:24 107

El detalle que convierte este incidente en un argumento de diseño es el yank de las versiones buenas, es decir, la retirada que bloquea nuevas instalaciones, pero deja intactos los proyectos que ya las tenían fijadas. Con las anteriores marcadas como retiradas y la envenenada disponible, un resolvedor que solo prefiera lo antiguo acaba cayendo en la trampa por eliminación. Por eso el mecanismo que estrena Cargo no es una preferencia blanda, sino una prohibición que puede terminar en error de resolución, y ese matiz separa una medida útil de un adorno. El descubrimiento inicial fue de la firma Nextron Systems, que avisó al equipo de Rust.

¿Qué se activa exactamente el 12 de noviembre?

La entrada del changelog de Cargo es explícita: se estabilizan las configuraciones de edad mínima de publicación, y cuando `registry.global-min-publish-age` está definido el resolvedor ignora las versiones publicadas más recientemente que esa ventana, salvo que ya estén en `Cargo.lock`. La propuesta nació como RFC 3923, el PR de estabilización #17335 se fusionó el 28 de agosto y la issue de seguimiento llevaba abierta desde el 18 de mayo, tres meses antes del ataque.

Ajuste Por defecto Qué hace
`registry.global-min-publish-age` `»0″` Edad mínima global, en números con unidades (segundos, minutos, horas, días, semanas o meses)
`registries.<nombre>.min-publish-age` Hereda el global Excepción por registro, útil para un espejo interno de confianza
`resolver.incompatible-publish-age` `»deny»` `deny` excluye lo demasiado nuevo; `allow` desactiva el control

El alcance tiene huecos que conviene conocer antes de fiarse. La comprobación solo puede aplicarse donde hay marca de tiempo de publicación, así que quedan fuera las dependencias de git, las rutas locales y los registros que no guardan esa fecha. En `cargo install` sí se aplica la edad mínima, aunque ahí no sirve la variable de entorno que la desactiva, porque la tabla del resolvedor no interviene en esa ruta. Y el lockfile manda: una versión ya registrada en `Cargo.lock` se conserva aunque sea más joven que la ventana.

Hay incluso una discusión de nombres que ayuda a entender la intención. El RFC descarta la palabra inglesa cooldown porque suele describir un limitador de ritmo, y elige edad mínima de publicación porque habla de madurez. La prensa y medio mundo técnico han vuelto al atajo, y en español lo hemos traducido como cuarentena, pero ninguna opción de Cargo se llama así.

¿Por qué una ventana de horas sirve de algo?

El ataque del 20 de agosto duró entre 86 y 107 minutos, un margen ridículo para leer un diff y suficiente para que un proceso automático lo compilara. Ese es el hueco que cierra la ventana: no impide la publicación maliciosa, sino que aleja a los consumidores de las primeras horas, que son las únicas en las que el atacante tiene ventaja. El propio RFC lo plantea como una carrera entre escáneres automáticos, revisores humanos y la reacción del mantenedor legítimo.

Ese margen no depende del atacante, sino de cuánta gente ha actualizado ya. Una semana de espera no blinda al ecosistema entero, pero aleja del pozo a quien la configura en su proyecto.

La segunda parte del argumento es más sutil y explica la dureza del valor por defecto. Si Cargo se limitará a despriorizar lo nuevo y aceptarlo como último recurso, el atacante podría publicar la versión mala y retirar las buenas para forzar esa caída. Con `deny` el resolvedor se niega y devuelve un error, de modo que el proyecto se rompe de forma visible en vez de compilar algo envenenado en silencio. Los diagnósticos, además, dicen qué versión se ha saltado y cuántos días tiene.

El efecto secundario es contraintuitivo para un cambio de seguridad. Cuanto menos adoptada está una versión, más barato resulta retirarla después, porque menos lockfiles la han fijado. El RFC lo resume sin adornos: la ventana práctica de un yank la marca la adopción, no el calendario. Esperar unos días reduce el daño de un fallo y también el de la retirada que llega tarde.

Lo que se paga por llegar tarde a propósito

El precio está reconocido en el propio documento. Cualquier arreglo también es una versión nueva y también queda sujeto a la edad mínima, así que el equipo que necesita el parche hoy tiene que saltarse su propia política con una variable de entorno y un `cargo update –precise`. Una vez registrada en el lockfile, esa versión se queda ahí para las siguientes resoluciones. Es una decisión manual, tomada en el peor momento posible: con un aviso de seguridad abierto y el reloj corriendo.

La tensión con los avisos de vulnerabilidades es real, y no es culpa de Cargo. Los boletines de RUSTSEC señalan la versión que corrige el fallo, y esa versión, recién publicada, es justo lo que una ventana de siete días deja fuera.

Los parches de urgencia no se negocian con un calendario. El caso más reciente es rustup 1.29.2, publicado el 20 de octubre, que actualizó `rustls-webpki` y `webpki-root-certs` para tapar el fallo de los certificados intermedios de eMudhra. Ese tipo de prisa no admite espera.

El RFC admite la objeción de fondo y la llama su mayor inconveniente: si todo el mundo espera, los problemas tardan más en descubrirse. Su respuesta es que habrá una dispersión de valores y que algunos proyectos probarán las versiones nuevas en integración continua.

Un ecosistema que aprende a decidir con el reloj

Lo interesante del cambio no es la línea de configuración, sino que convierte la selección de dependencias en una política escrita y compartida por el equipo. Hasta ahora esa decisión vivía en la cabeza de quien lanzaba la actualización o en el bot que la proponía; a partir del 12 de noviembre puede estar en un fichero, con un número que el equipo discute y revisa.

La campaña que el equipo de crates.io describió el 17 de septiembre, con falsas ofertas de trabajo y videollamadas para comprometer cuentas de mantenedores, empuja en la misma dirección: el paquete legítimo de ayer es la vía de entrada de mañana. La guía de slopsquatting que publicamos el 17 de octubre cubre la otra mitad del problema, el del nombre que se parece demasiado al bueno.

Queda una pregunta que ningún valor por defecto resuelve, y es cuánto vale la pena esperar. Tres días frenan casi nada, treinta congelan al equipo en un ecosistema que publica a diario, y siete han sido el ejemplo que Rust puso sobre la mesa. La respuesta sensata depende de si tu código mueve dinero o solo pinta una interfaz, y de si tienes a alguien mirando los avisos el mismo día que salen.

José Maldonado

Autor

José Maldonado

Activista y bloguero de tecnología, software libre y blockchain.

Lo último
El eurodiputado Fernando Navarrete propone emitir deuda pública tokenizada

Una lectura semanal imprescindible sobre inteligencia artificial y blockchain.

Te explicamos cómo transforman la economía, qué nuevas puertas te abren y cómo te afectan.