Notification Center Database: macOS Notification History
The usernoted SQLite database keeps delivered macOS notifications with app, title, body and time, often preserving message text from other apps.
- Location
- ~/Library/Group Containers/group.com.apple.usernoted/db2/db
- Proves
- Which app showed which notification text to the user, and when it was delivered
- Timestamps
- Mac absolute time (seconds since 2001-01-01 UTC)
- Access
- Sequoia+: TCC-protected group container (Full Disk Access); older: owning user
- Retention
- Not documented by Apple; varies, measure from oldest delivered_date
- Collection
- mac_apt, Disk image, cp
Tools
Compare all tools- mac_aptCLI · open source
- PlasoCLI · open source
- sqlite3CLI · built into macOS
What it is
usernoted stores notifications delivered through Notification Center in a per-user SQLite database. Each row holds the sending app, the delivery time and a binary property list with the notification content: title, subtitle and body. Because apps put message previews in notifications, the database frequently preserves text from Messages, Mail, chat clients, two-factor codes and VPN or security tools, sometimes after the source data is gone.
Kinga Kieczkowska showed that the stored body can be longer than what the banner displayed. Researchers raised this as a privacy problem for years; in macOS Sequoia Apple moved the database into a TCC-protected group container.
Where it lives
| macOS version | Path | Protection |
|---|---|---|
| 10.9 Mavericks and earlier | ~/Library/Application Support/NotificationCenter/<UUID>.db | User |
| 10.10 Yosemite to 10.12 Sierra | /private/var/folders/<xx>/<id>/0/com.apple.notificationcenter/db/db | User's DARWIN_USER_DIR |
| 10.13 High Sierra to 14 Sonoma | /private/var/folders/<xx>/<id>/0/com.apple.notificationcenter/db2/db | User's DARWIN_USER_DIR, readable by the user |
| 15 Sequoia and later | ~/Library/Group Containers/group.com.apple.usernoted/db2/db | TCC-protected group container, needs Full Disk Access |
Upgraded systems can hold both db and db2 folders. Collect db-wal and db-shm with db. On a live pre-Sequoia system, getconf DARWIN_USER_DIR run as the user prints the /var/folders/... base.
What it proves
- An app delivered a notification with a given title, subtitle and body at a given time.
- Whether the notification was presented on screen (
presented). - Which apps were registered with Notification Center (
apptable), which is also a light indicator of installed software. - Text of messages, emails or alerts that may have been deleted from the originating app.
It does not prove the user read the notification, and it only captures what the app chose to put in the notification payload.
Key fields
Tables in the db2 schema include app, record, requests, delivered, displayed, snoozed, categories and dbinfo.
| Table.column | Meaning |
|---|---|
app.app_id | Primary key |
app.identifier | Bundle ID of the app, e.g. com.apple.MobileSMS |
record.app_id | Link to app.app_id |
record.uuid | Notification UUID |
record.data | Binary plist with the content |
record.presented | Whether it was shown |
record.delivered_date | Delivery time |
dbinfo.key / value | Schema info such as compatibleVersion |
Keys inside the record.data plist, as parsed by mac_apt and ishinobu:
| Key | Meaning |
|---|---|
app | Bundle ID |
date | Notification time (Mac absolute time) |
req | Dictionary with the request |
req.titl / req.subt / req.body | Title, subtitle, body text |
req.cate | Category |
req.iden | Request identifier |
req.durl | Default action URL, where present |
The app table has a delete trigger: removing an app row cascades to its rows in record, requests, delivered, displayed, snoozed and categories.
Timestamps
delivered_date and the plist date are Mac absolute time, seconds since 2001-01-01 UTC:
SELECT datetime(r.delivered_date + 978307200, 'unixepoch') AS delivered_utc,
a.identifier, r.presented, r.uuid
FROM record r LEFT JOIN app a ON a.app_id = r.app_id
ORDER BY r.delivered_date DESC;
Retention
Apple does not document a retention period or row limit. Rows for an app are also removed when its app row is deleted (the trigger above). Report the oldest and newest delivered_date you observe, and check the WAL for rows not yet checkpointed.
Collection
# Sequoia and later (Terminal with Full Disk Access)
cp -p ~/Library/Group\ Containers/group.com.apple.usernoted/db2/db* case/notifications/
# Before Sequoia, run as the user
cp -p "$(getconf DARWIN_USER_DIR)"com.apple.notificationcenter/db2/db* case/notifications/
- From an image, check every user's group container and every
/private/var/folders/*/*/0/com.apple.notificationcenter/folder. - mac_apt's
NOTIFICATIONSplugin selects the right location by macOS version and exports the database.
Parsing
- mac_apt
NOTIFICATIONSplugin decodes the plist into title, subtitle and message columns (also in artifact-only mode):
python3 mac_apt_artifact_only.py -i case/notifications/db -o out -c NOTIFICATIONS
- Plaso has a
mac_notificationcenterSQLite plugin. - Manual decoding of one row:
sqlite3 db "SELECT writefile('n.plist', data) FROM record ORDER BY delivered_date DESC LIMIT 1;"
plutil -p n.plist
Investigator tips
- Notification text is often the only surviving copy of a deleted chat message: compare with Messages and Mail.
- On Sequoia and later, a collection without Full Disk Access silently misses this file. Check tool logs.
- Search bodies for one-time codes, "new sign-in" and VPN or security product alerts to build an account or intrusion timeline.
- An
approw for an unexpected bundle ID shows that the app registered with Notification Center at some point; check whether it is still installed. - Biome
Notification.Usageand Screen Time notification counts give independent corroboration of notification activity. delivered_datereflects delivery on this Mac, which can differ from when a remote message was sent; compare with the source app's own timestamps.