Skip to content

.LastGKReject: Gatekeeper Rejection Evidence on macOS

What .LastGKReject records when Gatekeeper blocks an item, where it lives, how to collect it and how to tie the rejection back to a download.

Published on 7 min read

TL;DR. When Gatekeeper refuses to run an item, syspolicyd writes a small property list called .LastGKReject. It contains a bookmark to the rejected item (from which the target path and volume can be read), a TimeStamp and an XProtectMalwareType number. Only the last rejection is kept and the file may not exist at all, but when it is there it turns a quarantined download into a concrete, dated launch attempt that macOS blocked.

What Gatekeeper checks

Gatekeeper is implemented by the syspolicyd daemon. On the first launch of quarantined code it checks the code signature, whether the code is signed with a Developer ID, whether it is notarized, and it runs XProtect. The link to everything else in this series is the quarantine attribute: the checks are driven by the com.apple.quarantine value that the downloading app or Archive Utility left on the file. The flags guide explains that value in detail.

Two outcomes leave traces. If the user approves an item, the attribute gains the user-approved flag 0x0040, so a value such as 0083 becomes 00c3. From macOS 15 the old Control-click bypass is gone and overrides go through System Settings. If Gatekeeper refuses the item, syspolicyd records the failure in .LastGKReject.

What .LastGKReject is

.LastGKReject is a property list, XML or binary, written by syspolicyd when Gatekeeper refuses an item. In Apple's open-source Security code the writer is PolicyEngine::recordFailure. Two properties define how you should read it:

  • Only the last rejection is kept. Each new rejection replaces the previous record.
  • It may not exist. A Mac on which Gatekeeper never refused anything, or on which the file was removed, simply has none.

The file can also exist and be empty. On one live macOS 26.6 system we examined, /private/var/db/.LastGKReject was absent, while /private/var/db/SystemPolicyConfiguration/.LastGKReject existed as a world-readable, 181-byte XML property list holding an empty dictionary. Quarantine Parser reports that case as "no rejection recorded". This is an observation on one system, not a documented rule.

Where it lives

Apple's sources give two locations, and you should check both:

SourcePath
Older Apple sources/var/db/.LastGKReject (= /private/var/db/.LastGKReject)
Current Apple sources/var/db/SystemPolicyConfiguration/.LastGKReject

Both sit under system directories, so reading them needs root.

The keys

mac_apt's QUARANTINE plugin reads three keys, and Quarantine Parser reads the same information:

  • BookmarkData — a bookmark to the rejected item. A bookmark is a serialized file reference rather than a plain path string; the tool extracts the target path and the volume from it.
  • TimeStamp — when the rejection was recorded.
  • XProtectMalwareType — a number. mac_apt credits Patrick Wardle for this mapping: 2 unsigned app/program, 3 modified bundle, 5 signed app, 7 modified app. The mapping is reported, not documented by Apple.

Read XProtectMalwareType as a category of refusal, not as a malware family name. A value of 2 says the refused item was reported as unsigned; it does not say what the code does.

How to collect it

On a live Mac, from a Terminal with Full Disk Access, this command from the collection guide copies whichever of the two files exists:

for f in /private/var/db/.LastGKReject /private/var/db/SystemPolicyConfiguration/.LastGKReject; do
  [ -f "$f" ] && sudo cp -p "$f" ~/qcase/"$(basename "$(dirname "$f")")_LastGKReject"
done

The copies are named after their parent directory (db_LastGKReject or SystemPolicyConfiguration_LastGKReject), so you can tell which location each came from. Run it after creating ~/qcase and copying the quarantine database, as shown in the collection comparison. Hash the copies before you analyse them.

On a mounted image, mac_apt parses both the quarantine database and .LastGKReject:

python mac_apt.py -o out MOUNTED /Volumes/evidence QUARANTINE

In Quarantine Parser, drop the copied file alone or together with the database and an xattr listing. The tool accepts XML and binary property lists, shows the time, target path, volume and XProtectMalwareType, and raises a Gatekeeper rejection finding. Parsing happens in your browser; nothing is uploaded.

Chaining the rejection back to the download

On its own, .LastGKReject tells you that something was refused. Its value comes from joining it to the download that delivered it. A typical chain for a disk image has three links.

  1. The quarantine event. In QuarantineEventsV2 you find the disk image download: agent, URL and time. The database guide covers the columns.
  2. The mounted volume. Mounting a quarantined disk image marks its contents as quarantined, which is why the app inside went through Gatekeeper at all. The rejected path sits under /Volumes/<name>/, and the volume read from the bookmark names it.
  3. The rejected app. The TimeStamp should fall shortly after the download, and the app name should fit the image.

In the site's synthetic sample, the chain looks like this:

2026-09-14 10:26:30 UTC  QuarantineEventsV2  Safari  https://transfer.example/d/Qm7/sync-agent.dmg
2026-09-14 10:27:05 UTC  .LastGKReject       /Volumes/Sync Agent/SyncAgent.app  XProtectMalwareType 2

Thirty-five seconds separate the download from the refusal, the volume name matches the image, and the reported type is "unsigned app/program". None of the keys listed above is the quarantine UUID, so this link is built on name and time, not on a shared identifier. Say so in your report. The investigation walkthrough follows the same sample from end to end.

If the rejected item still exists on disk, read its com.apple.quarantine value too. A flag of 00c3 or 00c1 means it was later approved. A path under /private/var/folders/.../AppTranslocation/ in other evidence means a quarantined app ran from a randomized read-only mount; see App Translocation.

Limits

  • One record. Any later rejection, including a harmless one, overwrites the record you care about. Collect early.
  • No record is not a clean bill. The file may never have been written or may have been deleted.
  • Undocumented labels. The XProtectMalwareType mapping comes from third-party research.
  • A refusal is not the end of the story. The user could approve the item in System Settings, or the quarantine attribute could be removed with xattr -d com.apple.quarantine, after which Gatekeeper has nothing to assess. Look for the 0x0040 flag and for files that keep a WhereFroms but lost their quarantine attribute, as described in kMDItemWhereFroms vs quarantine.
  • The bookmark points to where the item was. A disk image volume disappears when it is unmounted; the path may no longer resolve.
  • No source URL. The record does not say where the item came from. That is what the quarantine database and attribute are for.

Where else to look

  • Unified logs. syspolicyd performs the assessment, so its log entries around the TimeStamp are the natural next source. They may show assessments and rejections that .LastGKReject, holding only the last one, no longer does.
  • ExecPolicy. On recent macOS, files can carry an 11-byte com.apple.provenance attribute, an opaque key into the ExecPolicy provenance_tracking table. Neither is decoded by Quarantine Parser; treat them as leads for further tooling.
  • Quarantine data. The event row for the image, the attribute on the image and on any copy of the app, and records recovered from free space if a row was deleted.

For a one-page overview of these artifacts, see the Gatekeeper and XProtect cheat sheet on our sister site.

Frequently asked questions

Does .LastGKReject list every item Gatekeeper has blocked?

No. It holds only the most recent rejection, so a later rejection replaces it. It may also be missing entirely, and its absence proves nothing about what was or was not blocked.

Where is .LastGKReject stored?

Older Apple sources use /var/db/.LastGKReject (the same file as /private/var/db/.LastGKReject); current Apple sources use /var/db/SystemPolicyConfiguration/.LastGKReject. Check both locations and copy the file with root privileges.

What does XProtectMalwareType mean?

It is a number stored in the record. mac_apt, crediting Patrick Wardle, reports 2 as an unsigned app or program, 3 as a modified bundle, 5 as a signed app and 7 as a modified app. Apple does not document these values, so treat the label as an indication, not a verdict.

Related articles