Summary
- In the iOS 27.0 sysdiagnose we analysed, the container manager's text logs are not in the archive. The same information sits in a JSON file,
logs/ContainerManager/state.json, about 4 MB. - The existing container artifact reads the text logs, so on that archive it returned nothing. The pull request adds three artifacts that read the JSON file: containers with a directory, containers without one, and the code-signing registrations.
- On the Lab's research image the reports hold 1,738, 419 and 1,799 rows, and each count equals a separate count of the file's records.
- A row is a lead, not proof. A container's birth time is not an install time, and a container with no directory cannot be called removed.
- 3artifacts added
- 1,738containers with a directory
- 419containers without a directory
- 1,799code-signing registrations
Cite as
Addai, D., SectraTEK Lab (2026). Container manager state in the iOS sysdiagnose: an iLEAPP contribution. iLEAPP pull request #2509, merged 19 October 2026. https://github.com/abrignoni/iLEAPP/pull/2509
Methodology note
Drafted with AI assistance and reviewed by the author before publication.
The issue
An examiner is asked whether an app was ever on a phone, and the only evidence is a sysdiagnose from a recent iPhone. In iLEAPP the container artifact, mobileContainerManager, answers that kind of question from the container manager's text logs. On the iOS 27.0 archive we studied, the two places it looks, logs/MobileContainerManager and logs/appinstallation, are not there. The report comes back empty and raises no error. An empty report reads as "nothing found", which is the wrong thing to conclude.
The records were not gone. They had moved. The archive carries logs/ContainerManager/state.json, a dump of the container manager daemon's state, and no artifact read it. Figure 1 shows the two layouts side by side.
Show this figure as a table
| Path in the archive | In the iOS 27.0 archive | Read by |
|---|---|---|
| logs/MobileContainerManager/* | Absent | Existing container artifact (returns nothing) |
| logs/appinstallation/* | Absent | Existing container artifact (returns nothing) |
| logs/ContainerManager/state.json | Present, about 4 MB | Three new artifacts |
Two cautions apply. First, the state file is not a drop-in replacement for the text logs: it is a snapshot of what the daemon holds at capture, with directory times, and it has no installer messages. Second, our wider comparison cannot say when the text logs disappeared. The two directories were already absent in the nine iOS 26 archives we hold, so the pull request's wording that iOS 27 archives "no longer carry" them is true of the reader's inputs, but our data cannot place the change at iOS 27. The state file is the part that is new.
The essence
The contribution reads the state file and produces three reports. The file has one section per scope: the system scope and one user. Each container is a record with an identifier, a directory name, a stored generation number and a directory entry. That entry either holds four times (birth, change, modify and last seen) or is null. The first report lists the records with a directory, with the times first so the rows reach the timeline. The second lists the records whose entry is null. The third lists the code-signing registration cache.
| Artifact | A row is | Main columns | Rows on the research image |
|---|---|---|---|
| Container Manager State | A container record that has a directory entry | Birth, change, modify and last-seen time (UTC); container class; identifier; host app for extensions; state; path; generation; scope | 1,738 |
| Container Manager State - No Directory | A container record whose directory entry is null | Container class; identifier; directory name; generation; scope; notes | 419 |
| Container Manager State - Registrations | One entry of the daemon's ordered code-signing cache | Identifier; container class; flags set; number of entitlement keys; position in cache | 1,799 |
The notes shipped with each artifact say what the file does not establish. A container with no directory could have been removed, replaced or not yet created, and the file cannot tell these apart. A registration is an identifier the daemon registered; on the research image these included extension and group identifiers, so it is not a list of installed apps. Entitlement values are not copied, only how many keys there are.
The maintainer's review changed two things, both in the direction of caution. A numeric time value is no longer turned into a date by guessing its unit, because a count of seconds in the Cocoa epoch also falls inside the Unix range and came out as a date in 1994; the cell stays blank and the raw value goes in the notes. Labels and notes were also reduced to what the file and the code show.
Our methodology
We started from the file, not from a specification. The layout was read on one iOS 27.0 archive and every statement was tied to a measurement. The directory name of a container (pathIdentifier) appears in the archive's installation log; the container's own uuid does not, and differs from the directory name. Of the 419 records with a null entry, 418 carry a generation of -1. Within the records that have a directory, the stored times never run backwards: the birth and modify times are not after the change time, which is not after the last-seen time (Figure 4).
The reader is written so that one damaged value costs one cell. It returns no rows for an empty or truncated file, rejects times outside a sane range instead of inventing a date, keeps unknown keys as text, and reports problems through the log and the notes column rather than raising. Figure 2 shows the path from file to reports.
Validation had three layers. First, an independent recount: a function that counts the file's records without using the row builder, compared with the report rows for each artifact. Figure 3 shows the result on the research image: the counts agree for the HTML, TSV and LAVA outputs and the timeline. Second, the same rows come out of a .tar.gz, an extracted folder and a folder holding the archive. Third, the repository's own checks for claim language, descriptions, paths and HTML safety pass, and the lint score on the changed files is 10.00 of 10.
Show this figure as a table
| Report | Records counted in the file | Rows in the report |
|---|---|---|
| Container Manager State | 1,738 | 1,738 |
| Container Manager State - No Directory | 419 | 419 |
| Container Manager State - Registrations | 1,799 | 1,799 |
Test data followed the project's rule that fixtures carry no case data. The first fixture is generated by a script with invented identifiers and covers the edge cases: numeric times, damaged input and unknown keys. During review the maintainer asked for a fixture with the real file's structure. We committed a copy of the research image's file with every identifying value replaced (bundle identifiers, team identifiers, UUIDs and host names) and all times shifted by one constant offset; nothing else in the file was changed. Both fixtures are registered test cases with recorded baselines. The first baselines used Windows path separators and failed on the project's Linux runner, so they were re-recorded; baselines recorded on Linux or macOS avoid the problem.
The value
The people who benefit are examiners and researchers who receive an iOS 27 sysdiagnose and want to know which containers the device knew about. The artifact answers a narrow question: which app data, app group, extension and system containers existed at capture, and when did each directory come into being? Extension containers carry the host app that the file pairs with them, so an extension can be tied to its app. The birth times go onto the timeline with every other source.
In a case this is a corroborating source, not a finding. A birth time near an event of interest is an indicator that the container was created then; it does not show who installed the app or that anyone opened it, and it can reflect a restore or a re-creation. Figure 4 sets out what the stored times do and do not carry.
The limits are as important as the rows. The 419 records without a directory are leads about containers that once had, or will have, a directory; they are not removals. An app without a row is not shown to be absent, because the file is a snapshot and records can be missing for reasons outside it. Only one iOS 27.0 archive was used, so the artifact says nothing about other versions, and it has not been run on more than one iOS 27 device.
Where this sits in the Lab's research
This contribution supports LAB-TR-0011, Log Compression and Archive Structure in the iOS 27.0 Sysdiagnose: An Early Observation Tested Against Ten Archives (Lab Technical Report, version 1.2, preprint, not peer reviewed; doi:10.5281/zenodo.23720069). Section 4.2 shows the state file is present only in the iOS 27.0 archive among ten, and Table 6 lists the row counts of this reader. The report is on the Lab site at LAB-TR-0011.
LAB-TR-0010, which describes the reading rules for these new files, is in preparation and has no public page yet. Until it appears, the method above rests on the pull request.
Sources
Every number and claim above, with where it came from. PR means the pull request text, its diff or its review thread.
| Number or claim | Where it came from |
|---|---|
| Three artifacts added; containers with a directory 1,738; without 419; registrations 1,799 | PR text, section Tested; artifact notes in scripts/artifacts/containerManagerState.py |
| Counts equal a separate count of the file's records; HTML, TSV, LAVA and timeline match | PR text, section Tested; function expected_counts in the diff; Figure 3 |
| state.json about 4 MB; the two text-log directories absent | PR text, first paragraph |
| Text-log directories already absent in nine iOS 26 archives; state file new relative to all nine | LAB-TR-0011 section 4.2 |
| 418 of 419 no-directory records carry generation -1 | Artifact notes in the diff (containerManagerStateNoDirectory) |
| Birth and modify times not after change time, which is not after last-seen time, in all 1,738 records | docs/containermanager_state.md in the sibling pull request, SAF #262 |
| Maintainer review: numeric times left blank (a Cocoa-epoch number came out as a date in 1994), labels trimmed, time columns first | PR review thread, maintainer comment |
| Real-structure fixture with identifying values replaced and times shifted by one offset; baselines re-recorded for the Linux runner | PR review thread |
| Lint 10.00 of 10; repository checks pass | PR text, section Tested |
| Merged 19 October 2026 | GitHub pull request metadata (mergedAt) |
| Reader rows on the iOS 27.0 archive in the paper | LAB-TR-0011 Table 6 |