Skip to content

User activityNetwork

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

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 versionPathProtection
10.9 Mavericks and earlier~/Library/Application Support/NotificationCenter/<UUID>.dbUser
10.10 Yosemite to 10.12 Sierra/private/var/folders/<xx>/<id>/0/com.apple.notificationcenter/db/dbUser's DARWIN_USER_DIR
10.13 High Sierra to 14 Sonoma/private/var/folders/<xx>/<id>/0/com.apple.notificationcenter/db2/dbUser's DARWIN_USER_DIR, readable by the user
15 Sequoia and later~/Library/Group Containers/group.com.apple.usernoted/db2/dbTCC-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 (app table), 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.columnMeaning
app.app_idPrimary key
app.identifierBundle ID of the app, e.g. com.apple.MobileSMS
record.app_idLink to app.app_id
record.uuidNotification UUID
record.dataBinary plist with the content
record.presentedWhether it was shown
record.delivered_dateDelivery time
dbinfo.key / valueSchema info such as compatibleVersion

Keys inside the record.data plist, as parsed by mac_apt and ishinobu:

KeyMeaning
appBundle ID
dateNotification time (Mac absolute time)
reqDictionary with the request
req.titl / req.subt / req.bodyTitle, subtitle, body text
req.cateCategory
req.idenRequest identifier
req.durlDefault 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 NOTIFICATIONS plugin selects the right location by macOS version and exports the database.

Parsing

  • mac_apt NOTIFICATIONS plugin 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_notificationcenter SQLite 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 app row for an unexpected bundle ID shows that the app registered with Notification Center at some point; check whether it is still installed.
  • Biome Notification.Usage and Screen Time notification counts give independent corroboration of notification activity.
  • delivered_date reflects delivery on this Mac, which can differ from when a remote message was sent; compare with the source app's own timestamps.

See also