Skip to content

QuarantineEventsV2 : l’historique des téléchargements macOS

Ce qu’enregistre la base macOS QuarantineEventsV2 : schéma LSQuarantineEvent, temps absolu Mac, numéros de type, jointure par UUID de l’xattr et limites.

Publié le 8 min de lecture

TL;DR. QuarantineEventsV2 est une base SQLite propre à chaque utilisateur, dans ~/Library/Preferences/, où macOS consigne les téléchargements effectués par les applications qui participent à la quarantaine : Safari, Chrome, Mail, Messages, AirDrop, entre autres. Son unique table, LSQuarantineEvent, indique quand (en temps absolu Mac), quelle application, depuis quelle URL, et fournit un UUID d’événement que l’on retrouve à la fin de l’attribut com.apple.quarantine du fichier téléchargé. Elle ne stocke pas le nom du fichier et ne voit jamais ce que curl, wget ou git ont récupéré.

Qu’est-ce que QuarantineEventsV2 ?

Lorsqu’une application qui participe à la quarantaine enregistre un élément venu du réseau, macOS appose l’attribut de quarantaine sur le fichier et, pour bon nombre de ces téléchargements, écrit un événement dans la base QuarantineEventsV2 de l’utilisateur. Le fichier se trouve ici, un par compte utilisateur :

~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2

Les lignes survivent généralement à la suppression du fichier téléchargé, et aucune purge automatique n’est documentée. La base décrit donc souvent des téléchargements dont les fichiers ont disparu depuis longtemps : c’est ce qui en fait, en pratique, un historique des téléchargements. Des lignes peuvent néanmoins être supprimées avec sqlite3, et le fichier entier peut être effacé.

Le schéma de LSQuarantineEvent

Voici le schéma observé sur un système macOS 26.6. Apple ne documente pas les colonnes ; les descriptions ci-dessous s’appuient sur leurs noms et sur les données observées.

ColonneTypeContenu
LSQuarantineEventIdentifierTEXT, clé primaire, non nulUUID de l’événement ; aussi dernier champ de l’attribut de quarantaine du fichier
LSQuarantineTimeStampREALHeure de l’événement en temps absolu Mac
LSQuarantineAgentBundleIdentifierTEXTIdentifiant de bundle de l’application qui a téléchargé
LSQuarantineAgentNameTEXTNom de l’application qui a téléchargé
LSQuarantineDataURLStringTEXTURL des données téléchargées
LSQuarantineSenderNameTEXTNom de l’expéditeur, quand l’élément vient d’une personne
LSQuarantineSenderAddressTEXTAdresse de l’expéditeur, dans le même cas
LSQuarantineTypeNumberINTEGERNuméro de type de l’événement (voir plus bas)
LSQuarantineOriginTitleTEXTTitre de la page d’origine
LSQuarantineOriginURLStringTEXTPage ou emplacement d’où est parti le téléchargement
LSQuarantineOriginAliasBLOBDonnées d’alias binaires

Deux index existent : LSQuarantineEventIndex sur l’identifiant et LSQuarantineTimeStampIndex sur l’horodatage. Sur ce système, la base utilisait le mode journal de rollback (journal_mode=delete) avec une taille de page de 4096 octets : attendez-vous à trouver un fichier -journal plus souvent qu’un -wal, mais collectez les deux.

Horodatages : le temps absolu Mac

LSQuarantineTimeStamp compte les secondes écoulées depuis le 2001-01-01 00:00:00 UTC, c’est-à-dire le temps absolu Mac. Ajoutez 978307200 pour obtenir un horodatage Unix. La colonne est déclarée REAL, mais SQLite peut stocker sur disque un REAL dont la valeur est entière sous forme d’entier, une optimisation de stockage documentée. Un parseur qui n’accepte que des flottants sur 8 octets lira mal ou ignorera ces lignes ; il doit accepter les deux encodages.

LSQuarantineTypeNumber

L’en-tête public d’Apple LSQuarantine.h liste six constantes de type dans cet ordre : WebDownload, OtherDownload, EmailAttachment, InstantMessageAttachment, CalendarEventAttachment, OtherAttachment. Les associer aux valeurs 0 à 5 dans cet ordre est l’interprétation couramment rapportée ; elle n’est pas documentée par Apple.

ValeurSignification rapportéeObservé sur macOS 26.6
0WebDownloadTéléchargements Chrome et Homebrew Cask
1OtherDownload
2EmailAttachment
3InstantMessageAttachmentMessages
4CalendarEventAttachment
5OtherAttachment
6Non documentéesharingd (AirDrop)
7Non documentéeUne ligne Safari

Considérez ce numéro comme un indice et fiez-vous aux colonnes d’agent pour savoir quelle application était en jeu.

Observations sur un système macOS 26.6 réel

Les points suivants proviennent d’un seul système de test. Ce sont des observations, pas des règles, mais elles expliquent bien des résultats déroutants :

  • Beaucoup de lignes, notamment de Chrome et de Messages, n’avaient aucune colonne d’URL renseignée.
  • Les téléchargements Safari portaient un UUID dans leur attribut de quarantaine mais n’avaient aucune ligne correspondante dans la base ces derniers mois.
  • Certaines valeurs d’attribut étaient 0081;00000000;; : ni heure, ni agent, ni UUID.
  • Dans l’attribut, les noms d’agent échappent les espaces en \x20, par exemple Microsoft\x20Word.
  • Quelques UUID étaient en minuscules.

La leçon : un UUID sans correspondance ou une URL vide sont fréquents et ne sont pas suspects en eux-mêmes.

Ce qu’une ligne prouve, et ce qu’elle ne prouve pas

Une ligne active montre qu’une application participant à la quarantaine a enregistré un événement de téléchargement ou de pièce jointe, pour ce compte utilisateur, à cette heure, avec ces URL et cet agent. Rapprochée de l’attribut d’un fichier, elle relie un fichier sur disque à une source.

Elle ne montre pas que le fichier existe encore, qu’il a été ouvert ou exécuté, ni qui était au clavier : une session volée produit les mêmes lignes que l’utilisateur légitime. Elle ne nomme pas le fichier local. Et l’absence de ligne est une preuve faible : les téléchargements faits par des outils qui ne participent pas à la quarantaine (curl, wget, scp, rsync, git) n’y figurent jamais, des lignes peuvent être supprimées avec sqlite3 et le fichier lui-même peut être effacé.

Interroger la base avec sqlite3

Travaillez sur une copie, jamais sur l’original (ouvrir une base peut la modifier ; voir récupérer les enregistrements supprimés). Cette requête liste les événements dans l’ordre chronologique avec une date UTC :

SELECT datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') AS event_utc,
       LSQuarantineAgentBundleIdentifier, LSQuarantineDataURLString,
       LSQuarantineOriginURLString, LSQuarantineEventIdentifier
FROM LSQuarantineEvent ORDER BY LSQuarantineTimeStamp;

Relier les événements aux fichiers par l’UUID de l’xattr

La base connaît les URL mais pas les fichiers ; le fichier connaît son événement mais pas toujours son URL. Le lien, c’est l’UUID. Un attribut de quarantaine ressemble à ceci :

0083;66f7c2a1;Safari;6E4C2B1A-...

Le dernier champ correspond à LSQuarantineEventIdentifier. Comparez sans tenir compte de la casse, puisque certains UUID sont stockés en minuscules. Un événement peut correspondre à plusieurs fichiers : l’Utilitaire d’archive recopie la valeur de quarantaine de l’archive, UUID compris, sur chaque fichier qu’il extrait. Le champ des flags et les autres parties de la valeur sont détaillés dans les flags de com.apple.quarantine, et l’attribut d’URL posé par les navigateurs dans kMDItemWhereFroms ou quarantaine.

Comment Quarantine Parser lit la base

Quarantine Parser fonctionne entièrement dans votre navigateur (Rust compilé en WebAssembly dans un Web Worker) ; rien n’est envoyé. Il utilise son propre lecteur SQLite en lecture seule plutôt qu’une bibliothèque SQLite, ce qui compte à trois égards :

  • WAL. Un fichier -wal est appliqué comme le fait SQLite : les sommes de contrôle de l’en-tête et des frames ainsi que les sels sont vérifiés, et seules les frames validées sont utilisées. Un fichier -journal compagnon est lu aussi.
  • Copies partielles. Les copies tronquées, partielles ou verrouillées sont lues aussi loin que possible, au lieu d’échouer à la première erreur.
  • Récupération. Les lignes qui ne sont plus actives sont extraites par carving de l’espace libre des pages de table, des pages de la freelist, des anciennes copies de pages, des frames WAL remplacées ou non validées et des pages du journal de rollback. Voir récupérer les enregistrements QuarantineEventsV2 supprimés.

Vous pouvez déposer les bases de plusieurs utilisateurs à la fois, y compris les copies lsquarantine_<user> d’Aftermath, avec des listings xattr. Les vues Chronologie, Agents, Domaines et Fichiers affichent alors chaque événement avec les fichiers qui portent son UUID, et la vue Constats liste des pistes, pas des verdicts.

Pour aller plus loin

Questions fréquentes

Où se trouve la base QuarantineEventsV2 sur macOS ?

Chaque utilisateur en possède une dans ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2. C’est une base SQLite qui ne contient qu’une table, LSQuarantineEvent. Copiez-la avec les éventuels fichiers -wal ou -journal situés à côté.

QuarantineEventsV2 enregistre-t-elle le nom du fichier téléchargé ?

Non. Elle stocke des URL, l’application qui a téléchargé, un horodatage et un UUID d’événement, mais ni le chemin local ni le nom du fichier. Pour relier un événement à un fichier, rapprochez l’UUID situé à la fin de l’attribut com.apple.quarantine du fichier.

Comment convertir LSQuarantineTimeStamp en date ?

C’est du temps absolu Mac : des secondes depuis le 2001-01-01 00:00:00 UTC. Ajoutez 978307200 pour obtenir un temps Unix, par exemple avec datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') dans sqlite3.

Une base QuarantineEventsV2 vide signifie-t-elle qu’aucun téléchargement n’a eu lieu ?

Non. Des outils comme curl, wget, scp, rsync et git ne participent pas à la quarantaine, des lignes peuvent être supprimées avec sqlite3 et le fichier entier peut être effacé. L’absence est une preuve faible.

Articles liés