The Safari Cookies.binarycookies format
2026-06-21 · 3 min
Chrome and Firefox keep cookies in SQLite, so most tooling assumes a database. Safari does not — it writes a single packed binary file, and the values are cleartext rather than encrypted. The format is small enough to walk by hand, which is exactly what you want when no parser is at reach during triage.
Where it lives
On modern macOS the file is sandboxed under the Safari container:
~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies
Older builds kept it at ~/Library/Cookies/Cookies.binarycookies. If you
inherit a logical acquisition, search both paths — the container copy is
the live one.
File layout
Everything outside the cookie records is big-endian. The overall shape:
| Offset | Field | Notes |
|---|---|---|
| 0 | Magic cook | 4 bytes, ASCII |
| 4 | Page count | big-endian uint32 |
| 8 | Page sizes | big-endian uint32 each, one per page |
| ... | Page blocks | back to back, sizes as declared above |
So you read the magic, read how many pages exist, read that many 4-byte sizes, then the pages follow in order. Knowing each page size up front lets you carve pages without parsing their internals first.
Page structure
Each page is self-describing:
| Field | Notes |
|---|---|
| Page header | leading tag bytes |
| Cookie count | how many records on this page |
| Cookie offsets | one per cookie, relative to the page start |
| Cookie records | the records themselves |
The per-cookie offsets are the key to random access: jump to page start + offset and you are at the top of a record without scanning.
Cookie record
Each record opens with a fixed header (roughly 56 bytes) carrying the record size, a flags bitfield, and the byte offsets of the four strings that follow. After the header come four NUL-terminated strings, in order:
- domain
- name
- path
- value
Because the header stores the offset of each string, you do not guess boundaries — you seek. The flags bitfield encodes the usual cookie attributes:
| Flag | Meaning |
|---|---|
0x1 | Secure |
0x4 | HTTPOnly |
The exact bit set varies across Safari versions; treat unknown bits as informational rather than authoritative.
The values matter here. Where Chromium encrypts encrypted_value at rest,
Safari stores the cookie value in the clear — a session token or OAuth
state sits right there in the record, no keychain required.
Timestamps
Two timestamps live in the record header: creation and expiry. Both are
8-byte doubles (float64), not integers, and both are Mac absolute
time — seconds since 2001-01-01 00:00:00 UTC.
unix_seconds = mac_absolute_time + 978307200
This is the same epoch Safari's History.db uses, so cookie and history
timelines align without a second conversion.
One trap: an expiry of 0 means a session cookie, not the year 2001.
Render it as "session" rather than feeding 0 through the epoch math and
emitting 2001-01-01.
Parsing it by hand
A minimal walk, no SQLite involved:
- Confirm magic
cook. - Read the big-endian page count, then that many page sizes.
- For each page, read the cookie count and the per-cookie offsets.
- At each offset, read the header, pull the four string offsets and the two doubles, then slice out domain/name/path/value.
- Convert both doubles with
+ 978307200; map0expiry to session.
That is the entire format. The BrowserForensics parser does exactly this in the browser — no upload, no database engine.