knowledgeC.db Forensics: The Complete macOS Guide
What knowledgeC.db records on macOS, where it lives, how ZOBJECT and its streams work, how to convert its timestamps, and what the data does not prove.
TL;DR. knowledgeC.db is a SQLite database written by Apple's CoreDuet framework. It records macOS "pattern of life" events as intervals: an app in use, the display lit, the Mac locked or on power, a web domain visited. There is a system copy and a per-user copy, both in WAL mode. Every row of the ZOBJECT table belongs to a stream such as /app/usage and carries start and end times in Mac absolute time. It tells you which apps were used and when; it does not tell you who was at the keyboard. On current macOS many streams, including app focus, moved to Biome, so examine both sources.
What knowledgeC.db is
The knowledgeC database is a Core Data store, which is why its tables and columns carry a Z prefix. CoreDuet writes one row per event, grouped by stream. Unlike file system timestamps, which only say when a file changed, each row has a start and an end, so the database answers duration questions: how long was Terminal in use, when did the screen go dark, was the Mac on power during the night.
That makes it a timeline source, not a content source. You get bundle IDs, URLs, domains and on/off states. You do not get documents, keystrokes or commands.
Where it lives
| Scope | Path | Protection |
|---|---|---|
| System | /private/var/db/CoreDuet/Knowledge/knowledgeC.db | Owned by root, restricted by System Integrity Protection |
| User | ~/Library/Application Support/Knowledge/knowledgeC.db | TCC-protected: the collecting process needs Full Disk Access |
Both databases run in SQLite WAL mode. The companion files knowledgeC.db-wal and knowledgeC.db-shm must be collected with the main file: the most recent events often exist only in the WAL. Collection commands, UAC and Velociraptor options and the SIP trade-offs are covered in knowledgeC.db location and acquisition; what the WAL holds and how to read it is in recovering recent events from knowledgeC.db-wal.
The database structure
Three tables carry almost everything an examiner needs.
ZOBJECT: one row per event
ZOBJECT is the event table.
| Column | Meaning |
|---|---|
ZSTREAMNAME | Stream, for example /app/usage or /display/isBacklit |
ZVALUESTRING | Main string value, usually a bundle ID or a URL |
ZVALUEINTEGER / ZVALUEDOUBLE | Numeric value for state or measured streams |
ZSTARTDATE / ZENDDATE | Interval start and end |
ZCREATIONDATE | When the row was written |
ZSECONDSFROMGMT | Device UTC offset in seconds at event time |
ZSTARTDAYOFWEEK | Day of week, 1 = Sunday |
ZUUID | Event UUID |
ZSOURCE | Link to the ZSOURCE table |
ZSTRUCTUREDMETADATA | Link to the ZSTRUCTUREDMETADATA table |
ZSOURCE: who produced the event
ZSOURCE describes the producer, with columns such as ZBUNDLEID and ZDEVICEID. APOLLO reads ZDEVICEID as the identifier of the originating device, which matters because rows synced from another device on the same Apple Account can appear in the database. Group by device before attributing anything to the Mac under examination.
ZSTRUCTUREDMETADATA: stream-specific extras
ZSTRUCTUREDMETADATA holds extra columns per stream, for example Z_DKDIGITALHEALTHMETADATAKEY__WEBDOMAIN and Z_DKDIGITALHEALTHMETADATAKEY__WEBPAGEURL for web usage, Z_DKSAFARIHISTORYMETADATAKEY__TITLE for Safari history, or Z_DKNOTIFICATIONUSAGEMETADATAKEY__BUNDLEID for notifications.
Columns drift between macOS releases. Read the schema of the copy you have before running a canned query.
Streams you will meet
Values below are those used by APOLLO's modules.
| Stream | Value | Meaning |
|---|---|---|
/app/usage | ZVALUESTRING = bundle ID | App in use, with start and end |
/app/inFocus | ZVALUESTRING = bundle ID | App in focus |
/device/isLocked | ZVALUEINTEGER 1 / 0 | 1 = locked, 0 = unlocked |
/display/isBacklit | ZVALUEINTEGER 1 / 0 | 1 = display lit |
/device/isPluggedIn | ZVALUEINTEGER 1 / 0 | 1 = on power |
/safari/history | ZVALUESTRING = URL | Page title in structured metadata |
/app/webUsage | bundle ID | Domain and URL in structured metadata |
/notification/usage | ZVALUESTRING = notification type | Bundle ID in structured metadata |
APOLLO's macOS modules also cover streams such as /app/activity, /app/intents, /app/mediaUsage and /bluetooth/isConnected. Not every Mac has every stream, so enumerate what is actually present first:
SELECT ZSTREAMNAME, COUNT(*),
datetime(MIN(ZSTARTDATE)+978307200,'unixepoch') AS first_utc,
datetime(MAX(ZSTARTDATE)+978307200,'unixepoch') AS last_utc
FROM ZOBJECT GROUP BY ZSTREAMNAME ORDER BY 2 DESC;
Timestamps and time zones
ZSTARTDATE, ZENDDATE and ZCREATIONDATE are Mac absolute time: seconds, often fractional, since 2001-01-01 00:00:00 UTC. Add 978307200 to get Unix seconds.
The stored values are UTC. ZSECONDSFROMGMT records the offset the device applied at the time, so you can show local time without guessing the time zone, and a change in that value between events is a useful travel or time zone indicator.
SELECT datetime(ZSTARTDATE + 978307200, 'unixepoch') AS start_utc,
datetime(ZSTARTDATE + ZSECONDSFROMGMT + 978307200, 'unixepoch') AS start_local
FROM ZOBJECT LIMIT 5;
Report UTC as the reference and show local time next to it. Mixing the two in one timeline is the most common source of one-hour or two-hour errors.
Retention
Sarah Edwards (mac4n6) documented roughly four weeks of events in knowledgeC. Pruning is per stream and varies between releases, so do not quote a number from the literature in a report: the oldest ZSTARTDATE per stream in the query above is the effective retention window on your evidence, and it belongs in your findings.
knowledgeC and Biome
On recent macOS releases Apple has moved many streams to Biome, which stores them as SEGB files of protobuf records; app focus, for example, is expected in App.InFocus rather than in /app/inFocus. The stream-by-stream mapping and what to expect per release are in knowledgeC vs Biome.
What it proves, and what it does not
knowledgeC supports statements such as:
- this bundle ID was in use or in focus from this time to that time;
- the display was lit, the Mac was unlocked, or it was on power, during this interval;
- this domain or URL was visited in this app, where the web streams exist.
It does not prove:
- who was at the keyboard. An unlocked Mac with an app in focus is consistent with someone using it, not with a named person. The method for building that argument, and its limits, is in who was at the keyboard;
- what was done inside an app. There are no file names and no command lines;
- active use in every case. An app "in use" can be a window left in front of an idle screen. Pair app intervals with
/display/isBacklitand/device/isLocked; - wiping, when a stream is empty. On a recent Mac an empty
/app/inFocusis expected.
Also separate local rows from rows synced from other devices using ZSOURCE.ZDEVICEID.
Parsing options
| Tool | What it does |
|---|---|
| APOLLO | SQL modules per stream, merged into a timeline |
| Plaso | sqlite/mac_knowledgec parser for super-timelines |
| Velociraptor | Exchange artifact MacOS.Applications.KnowledgeC, which parses /app/activity and /app/inFocus rows in place |
| sqlite3 | Ad hoc SQL, on a copy only |
| KnowledgeC Parser | In the browser: applies the WAL, flags WAL-only and WAL-removed rows, merges knowledgeC with Biome |
Whatever you use, work on copies. Opening a WAL database with a normal SQLite client can checkpoint it, which rewrites the main file and can empty the WAL you wanted to examine.
A short example (synthetic)
The sample collection shipped with the tool is fictional. On the MacBook FIN-MBP-03, user dana.whitlock, the system database shows a /device/isLocked unlock at 10:04:31 UTC on 2026-09-14, with ZSECONDSFROMGMT = 7200, so 12:04 local time, while she was at lunch. /app/usage then shows Terminal, System Settings and Finder, and the user database shows /app/webUsage for transfer.example, before a lock at 10:49 UTC. Every one of those rows is in the -wal, not in the main file. Without the WAL the incident window would look empty.
What this establishes: the Mac was unlocked and those apps were in use during that window. What it does not establish: that Dana, or anyone in particular, did it. To load the sample and follow the same steps, see analyze knowledgeC and Biome in your browser.
FAQ
Where is knowledgeC.db on macOS?
There are two copies. The system database is /private/var/db/CoreDuet/Knowledge/knowledgeC.db (owned by root and SIP-restricted) and the user database is ~/Library/Application Support/Knowledge/knowledgeC.db (TCC-protected, so the collecting process needs Full Disk Access). Both run in WAL mode, so collect knowledgeC.db-wal and knowledgeC.db-shm with the main file.
How do I convert knowledgeC timestamps?
ZSTARTDATE, ZENDDATE and ZCREATIONDATE are Mac absolute time: seconds since 2001-01-01 00:00:00 UTC. Add 978307200 to get Unix time. The stored values are UTC; ZSECONDSFROMGMT gives the offset, in seconds, that the device used at the time of the event.
Why is /app/inFocus empty on a recent Mac?
On current macOS releases app focus is expected in the Biome stream App.InFocus rather than in knowledgeC, so a sparse or empty /app/inFocus is normal and is not evidence of wiping. Also check that the -wal file was collected, because the most recent rows often exist only there.
Does knowledgeC.db prove who used the Mac?
No. It shows that an app was in use or in focus, that the display was lit or the Mac unlocked, at given times. It does not identify the person at the keyboard, and it does not record file names or command lines. Attribution needs corroboration from other artifacts.