Skip to content

ExecutionPersistence

InstallHistory.plist: macOS Software Install Log

InstallHistory.plist and the package receipt database show which apps, packages and system updates were installed on a Mac, by which process and when.

Location
/Library/Receipts/InstallHistory.plist
Proves
Which software packages and updates were installed, when, and through which installer process
Timestamps
Plist date values (UTC); receipt install-time in Unix epoch seconds
Access
Readable by all users; InstallHistory.plist is writable by the admin group
Retention
Until deleted; receipts until pkgutil --forget or removal
Collection
UAC, Aftermath, mac_apt, ditto

What it is

macOS keeps two records of software installation. InstallHistory.plist is an append-only array with one entry per install session: macOS updates, App Store apps, security data updates (XProtect, Gatekeeper, MRT) and every flat package run through Installer or the installer command. Separately, the Installer "receipt" database keeps a .plist and a .bom (bill of materials) per installed package identifier, which pkgutil queries.

Together with /var/log/install.log they answer when a package arrived and which files it laid down. Apps installed by dragging an .app bundle to /Applications do not appear in either; for those, rely on quarantine events, Spotlight metadata and file system times.

Where it lives

ArtifactPathNotes
Install history/Library/Receipts/InstallHistory.plistBinary or XML plist, array of dictionaries
Third-party receipts/private/var/db/receipts/<package-id>.plist and .bomWritten by Installer for non-Apple packages
Apple component receipts/Library/Apple/System/Library/Receipts/From 10.15 Catalina: XProtect, MRT, Gatekeeper data, Rosetta, Command Line Tools
Legacy system receipts/System/Library/Receipts/Before Catalina; now on the read-only system volume and empty
Installer log/private/var/log/install.logSee system logs

All of these are world-readable on current releases. On a dead-box image look under /System/Volumes/Data/. Since Catalina, full macOS updates no longer leave per-package receipts or BOM files, so OS updates are visible in InstallHistory.plist and install.log but not in the receipt database.

What it proves

  • A named package or update (displayName, displayVersion) was installed at a specific time.
  • Which process performed it (processName), separating user-driven installs from background updates.
  • Which package identifiers were part of it (packageIdentifiers), the key into the receipt database.
  • Via the receipt: the install volume and prefix, the installing process, and via the BOM, every file and folder the package installed with owner, mode and size.

It does not prove that the installed software was ever launched, that it is still present, or who approved the install. Uninstalling an app usually leaves both the history entry and the receipt behind.

Key fields

InstallHistory.plist entries:

KeyMeaning
dateInstall time
displayNameProduct or package name
displayVersionVersion string
packageIdentifiersArray of package IDs (absent for some entries)
processNameInstalling process
contentTypePresent for some entries, e.g. config-data for security data updates

processName values seen on current releases include installer (command line), Installer (GUI), softwareupdated, appstoreagent, XProtectUpdateService, bootinstalld and CoreServicesUIAgent. A processName of installer often means a script, MDM agent or an attacker ran installer -pkg.

Receipt plist keys: PackageIdentifier, PackageVersion, PackageFileName, InstallDate, InstallPrefixPath, InstallProcessName.

Timestamps

date and InstallDate are plist date values, stored in binary plists as Mac absolute time (seconds since 2001-01-01 UTC) and shown in UTC by plutil -p. pkgutil --pkg-info prints install-time as Unix epoch seconds:

pkgutil --pkg-info com.example.agent
date -u -r 1773637885     # convert install-time to UTC

Retention

InstallHistory.plist is not rotated and keeps growing, and it survives major upgrades (a Mac upgraded from 15 Sequoia to 26 Tahoe keeps its Sequoia-era entries), so it can reach back to the initial setup of the system. Receipts stay until the package is forgotten (pkgutil --forget <id>, which keeps the installed files) or the file is deleted. Receipts need root to change, but InstallHistory.plist is root:admin mode 664 on current releases, so any admin account can edit it without sudo. Older copies may exist in APFS snapshots and Time Machine.

Collection

sudo ditto /Library/Receipts/InstallHistory.plist ./case/
sudo ditto /private/var/db/receipts ./case/receipts
sudo ditto /Library/Apple/System/Library/Receipts ./case/apple_receipts
pkgutil --pkgs > ./case/pkgutil_pkgs.txt            # live only
  • UAC collects InstallHistory.plist (files/packages/installed_applications.yaml) and runs pkgutil --packages live.
  • Aftermath parses InstallHistory.plist into CSV in its system recon module.
  • mac_apt INSTALLHISTORY plugin extracts and parses it from an image.

Parsing

python3 - <<'PY'
import plistlib
for e in plistlib.load(open('InstallHistory.plist', 'rb')):
    print(e['date'].isoformat(), e.get('processName'), e.get('displayName'),
          e.get('displayVersion'), ','.join(e.get('packageIdentifiers', [])), sep='\t')
PY

pkgutil --files com.example.agent                 # file list from the BOM
lsbom -p MUGsf /private/var/db/receipts/com.example.agent.bom
  • mac_apt: python mac_apt.py -o out E01 mac.E01 INSTALLHISTORY.
  • Plaso macos_install_history plist plugin: log2timeline.py --parsers 'plist/macos_install_history' --storage-file ih.plaso InstallHistory.plist.
  • pkgutil --volume /Volumes/Image --pkgs lists receipts on a mounted image.

Investigator tips

  • Sort by date and look at third-party packages installed with processName = installer around the incident: packaged malware and remote-management tools often arrive that way.
  • Resolve packageIdentifiers to the receipt and BOM to list every file the package dropped, especially under /Library/LaunchDaemons, /Library/LaunchAgents and /Library/PrivilegedHelperTools. Pivot to launchd jobs.
  • Match the time against install.log: the Installer process records Opened from: <path>.pkg, which gives the package's original location, and package_script_service lines show preinstall and postinstall script output.
  • Tie the downloaded .pkg to quarantine events and to syspolicyd assessments in the Unified Logs.
  • XProtectUpdateService and config-data entries date security data updates, which helps establish which XProtect version protected the Mac at a given time.
  • Because admins can rewrite InstallHistory.plist without sudo, check its modification time and compare entries with install.log and receipts. A receipt without a matching history entry, or a history entry for a package ID with no receipt, suggests pkgutil --forget or manual deletion.

See also