Skip to content

QuarantineEventsV2: historial de descargas en macOS

Qué registra la base QuarantineEventsV2 de macOS: esquema de LSQuarantineEvent, tiempo absoluto de Mac, números de tipo, unión por UUID del xattr y límites.

Publicado el 8 min de lectura

TL;DR. QuarantineEventsV2 es una base SQLite por usuario, en ~/Library/Preferences/, donde macOS registra las descargas realizadas por las aplicaciones que participan en la cuarentena, como Safari, Chrome, Mail, Mensajes y AirDrop. Su única tabla, LSQuarantineEvent, indica cuándo (en tiempo absoluto de Mac), qué aplicación, desde qué URL y aporta un UUID de evento que también cierra el atributo com.apple.quarantine del archivo descargado. No guarda el nombre del archivo y nunca ve lo que se obtuvo con curl, wget o git.

Qué es QuarantineEventsV2

Cuando una aplicación que participa en la cuarentena guarda algo procedente de la red, macOS marca el archivo con el atributo de cuarentena y, en muchas de esas descargas, escribe un evento en la base QuarantineEventsV2 del usuario. El archivo está aquí, uno por cuenta de usuario:

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

Las filas suelen sobrevivir al borrado del archivo descargado y no hay ninguna purga automática documentada. Por eso la base describe a menudo descargas cuyos archivos desaparecieron hace tiempo, y en la práctica funciona como un historial de descargas. Aun así, las filas se pueden borrar con sqlite3 y el archivo entero se puede eliminar.

El esquema de LSQuarantineEvent

Este es el esquema observado en un sistema macOS 26.6. Apple no documenta las columnas; las descripciones siguientes se basan en sus nombres y en los datos observados.

ColumnaTipoContenido
LSQuarantineEventIdentifierTEXT, clave primaria, no nuloUUID del evento; también es el último campo del atributo de cuarentena del archivo
LSQuarantineTimeStampREALHora del evento en tiempo absoluto de Mac
LSQuarantineAgentBundleIdentifierTEXTIdentificador de bundle de la aplicación que descargó
LSQuarantineAgentNameTEXTNombre de la aplicación que descargó
LSQuarantineDataURLStringTEXTURL de los datos descargados
LSQuarantineSenderNameTEXTNombre del remitente, cuando el elemento procede de una persona
LSQuarantineSenderAddressTEXTDirección del remitente, en el mismo caso
LSQuarantineTypeNumberINTEGERNúmero de tipo del evento (ver más abajo)
LSQuarantineOriginTitleTEXTTítulo de la página de origen
LSQuarantineOriginURLStringTEXTPágina o ubicación desde la que empezó la descarga
LSQuarantineOriginAliasBLOBDatos de alias binarios

Hay dos índices: LSQuarantineEventIndex sobre el identificador y LSQuarantineTimeStampIndex sobre la marca de tiempo. En ese sistema la base usaba el modo de diario de rollback (journal_mode=delete) con un tamaño de página de 4096 bytes, así que es más probable encontrar un archivo -journal que un -wal, pero recoja ambos.

Marcas de tiempo: tiempo absoluto de Mac

LSQuarantineTimeStamp cuenta los segundos transcurridos desde el 2001-01-01 00:00:00 UTC, es decir, el tiempo absoluto de Mac. Sume 978307200 para obtener una marca de tiempo Unix. La columna se declara REAL, pero SQLite puede guardar en disco como entero un REAL cuyo valor es un número entero, una optimización de almacenamiento documentada. Un parser que solo acepte flotantes de 8 bytes leerá mal u omitirá esas filas; tiene que aceptar ambas codificaciones.

LSQuarantineTypeNumber

La cabecera pública de Apple LSQuarantine.h enumera seis constantes de tipo en este orden: WebDownload, OtherDownload, EmailAttachment, InstantMessageAttachment, CalendarEventAttachment, OtherAttachment. Asignarlas a los valores 0 a 5 en ese orden es la interpretación que se suele citar; Apple no la documenta.

ValorSignificado citadoObservado en macOS 26.6
0WebDownloadDescargas de Chrome y Homebrew Cask
1OtherDownload
2EmailAttachment
3InstantMessageAttachmentMensajes
4CalendarEventAttachment
5OtherAttachment
6No documentadosharingd (AirDrop)
7No documentadoUna fila de Safari

Trate el número como un indicio y apóyese en las columnas del agente para saber qué aplicación intervino.

Observaciones en un sistema macOS 26.6 real

Lo siguiente procede de un único sistema de prueba. Son observaciones, no reglas, pero explican muchos resultados desconcertantes:

  • Muchas filas, sobre todo de Chrome y Mensajes, no tenían ninguna columna de URL rellenada.
  • Las descargas de Safari llevaban un UUID en su atributo de cuarentena pero no tenían fila correspondiente en la base en los últimos meses.
  • Algunos valores del atributo eran 0081;00000000;;: sin hora, sin agente, sin UUID.
  • En el atributo, los nombres de agente escapan los espacios como \x20, por ejemplo Microsoft\x20Word.
  • Algunos UUID estaban en minúsculas.

La lección: un UUID sin correspondencia o una URL vacía son habituales y no resultan sospechosos por sí solos.

Qué prueba una fila y qué no

Una fila viva muestra que una aplicación que participa en la cuarentena registró un evento de descarga o de adjunto, para esta cuenta de usuario, a esa hora, con esas URL y ese agente. Unida al atributo de un archivo, vincula un archivo en disco con un origen.

No demuestra que el archivo siga existiendo, que se abriera o ejecutara, ni quién estaba ante el teclado: una sesión robada produce las mismas filas que el usuario legítimo. No nombra el archivo local. Y la ausencia de una fila es una prueba débil: las descargas hechas con herramientas que no participan (curl, wget, scp, rsync, git) nunca aparecen, las filas se pueden borrar con sqlite3 y el propio archivo se puede eliminar.

Consultar la base con sqlite3

Trabaje sobre una copia, nunca sobre el original (abrir una base puede modificarla; vea recuperar registros borrados). Esta consulta lista los eventos por orden cronológico con una fecha UTC:

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

Unir eventos y archivos mediante el UUID del xattr

La base conoce las URL pero no los archivos; el archivo conoce su evento pero no siempre su URL. El vínculo es el UUID. Un atributo de cuarentena tiene este aspecto:

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

El último campo coincide con LSQuarantineEventIdentifier. Compare sin distinguir mayúsculas y minúsculas, porque algunos UUID se guardan en minúsculas. Un evento puede corresponder a varios archivos: la Utilidad de Archivo copia el valor de cuarentena del archivo comprimido, UUID incluido, en cada archivo que extrae. El campo de flags y las demás partes del valor se explican en los flags de com.apple.quarantine, y el atributo de URL que ponen los navegadores en kMDItemWhereFroms frente a la cuarentena.

Cómo lee la base Quarantine Parser

Quarantine Parser se ejecuta por completo en su navegador (Rust compilado a WebAssembly en un Web Worker); no se sube nada. Usa su propio lector SQLite de solo lectura en lugar de una biblioteca SQLite, lo que importa en tres aspectos:

  • WAL. Un archivo -wal se aplica como lo hace SQLite: se verifican las sumas de comprobación de la cabecera y de los frames y las sales, y solo se usan los frames confirmados. También se lee el archivo -journal asociado.
  • Copias parciales. Las copias truncadas, parciales o bloqueadas se leen hasta donde llegan, en lugar de fallar en el primer error.
  • Recuperación. Las filas que ya no están vivas se extraen mediante carving del espacio libre de las páginas de tabla, de las páginas de la freelist, de copias antiguas de páginas, de frames WAL sustituidos o no confirmados y de páginas del diario de rollback. Vea recuperar registros borrados de QuarantineEventsV2.

Puede arrastrar a la vez las bases de varios usuarios, incluidas las copias lsquarantine_<user> de Aftermath, junto con listados de xattr. Las vistas Cronología, Agentes, Dominios y Archivos muestran entonces cada evento con los archivos que llevan su UUID, y la vista Hallazgos enumera pistas, no veredictos.

Lecturas relacionadas

Preguntas frecuentes

¿Dónde está la base QuarantineEventsV2 en macOS?

Cada usuario tiene una en ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2. Es una base SQLite con una sola tabla, LSQuarantineEvent. Cópiela junto con cualquier archivo -wal o -journal que haya a su lado.

¿Registra QuarantineEventsV2 el nombre del archivo descargado?

No. Guarda URL, la aplicación que descargó, una marca de tiempo y un UUID de evento, pero no la ruta local ni el nombre del archivo. Para vincular un evento con un archivo, compare el UUID que aparece al final del atributo com.apple.quarantine del archivo.

¿Cómo convierto LSQuarantineTimeStamp en una fecha?

Es tiempo absoluto de Mac: segundos desde el 2001-01-01 00:00:00 UTC. Sume 978307200 para obtener tiempo Unix, por ejemplo con datetime(LSQuarantineTimeStamp + 978307200, 'unixepoch') en sqlite3.

¿Una QuarantineEventsV2 vacía significa que no se descargó nada?

No. Herramientas como curl, wget, scp, rsync y git no participan en la cuarentena, las filas se pueden borrar con sqlite3 y el archivo entero se puede eliminar. La ausencia es una prueba débil.

Artículos relacionados