Summary
The pull request adds three artifacts to iLEAPP's logarchive module. They list the lines in which the camera capture daemon records a capture session being created, started, changed or stopped; the daemon's streaming power events; and SpringBoard's recording-indicator lines. Fields are copied as logged and no meaning is given to the numbers.
The most useful finding is a warning. On one public-corpus image, every capture-session row belonged to a system daemon that uses the camera for face authentication. None came from the Camera app or a third-party app. A start line is a lead, and it does not establish that audio or video was recorded.
- 3artifacts added
- 316lines of parser code
- 1,656lines added across 7 files
- 102session rows on one image, all from one system daemon
Methodology note
Drafted with AI assistance and reviewed by the author before publication.
The issue
An examiner asks whether the camera or microphone was used around a time of interest. The unified log has a camera capture daemon that writes a line when a session is created and when a process starts or stops it. It looks like a direct answer.
It is not. On a public-corpus image with iOS 26.5.2, the parser found 102 session rows, and every row that carried an app ID named the same Apple system daemon, which uses the camera for biometric authentication. The other rows were its own lines or lines with the same PID. None came from the Camera app or from a third-party app, even though the import also held lines of the Camera app and of a third-party app in shapes the artifact does not collect. A search that took every line from the camera daemon as camera use would have reported activity that was face authentication.
Counting lines goes wrong in a second way. In the iOS 27.0 log that the notes describe, torch lines from the same daemon outnumbered the session lines by a wide margin. A count of lines from that daemon is not a count of camera use.
Show this figure as a table
| Group | Rows |
|---|---|
| App ID names the face-authentication daemon | 78 |
| No app ID: same PID or its own lines | 24 |
| Camera app | 0 |
| Third-party app | 0 |
The essence
The contribution reads the daemon's session lifecycle, its streaming power events and SpringBoard's recording-indicator lines, and copies each field as logged. A start line is shown as a lead. State lines are listed only when the started flag or a stolen flag is 1, and idle state lines are collected but not listed.
| Artifact | Lines read | Fields added |
|---|---|---|
logarchive_capture_sessions | Session create, start and stop, status transition, detach, start and stop written by the app itself | Function, app ID, PID, session object, started, camera and audio flags, background, stolen flags, errors, status, transition |
logarchive_capture_streaming | Camera streaming power event | PID, application ID, device type, streaming flag, port type |
logarchive_recording_indicator | SpringBoard activity-changed, layer creation, rendering assertion | The whole message as logged |
The streaming lines carried the application ID as <private> in the logs read, so the app has to be taken from the PID, for example by matching it with the PID in the session artifact. That match was not validated against a known capture.
Our methodology
Message shapes. The shapes were read on decoded iOS 26.6 and 27.0 sysdiagnose logs, then fixed as literal markers. The collection step requires the subsystem as well as the text, because the same subsystem carried tens of thousands of torch and device-vendor lines that no marker matches (Figure 2).
Synthetic fixture and tests. A synthetic log show export holds every branch, plus decoy lines under other subsystems. Nineteen tests in four groups run it through the real two-stage chain: the run and database, the collection markers, the parser, and the fixture. One test fails if a marker is removed from the collection step. Another checks that torch and vendor lines reach no artifact. A small change to make_test_data.py lets a module whose artifacts declare no paths be cased without raising.
Behavior on missing or odd data. A line in another shape gives blanks rather than a guess, and the full message stays in the row (Figure 3). A flag name inside another word is not read. A transition needs an arrow. The Session Object address can be reused, so the notes tell the reader to compare times as well.
Validation on real images. Run on two corpus sysdiagnoses, the counts are in sample_data: 102 session rows and 12 streaming rows on the iOS 26.5.2 image, none on the iOS 26.6 image, where the import held 1,140 lines of the camera subsystem and none in these shapes. The recording-indicator artifact gave 1 row on iOS 26.6, an empty activity set, and none on iOS 26.5.2, where all 51 indicator-prefix lines were opacity or force-layer lines. App capture, the audio flag and Mic Stolen at 1 are exercised only by the synthetic fixture, and the notes say so.
Show this figure as a table
| Artifact | Columns |
|---|---|
| logarchive_capture_sessions | 18 |
| logarchive_capture_streaming | 10 |
| logarchive_recording_indicator | 5 |
Show this figure as a table
| Message part | Column |
|---|---|
| captureSession_updateRunningCondition | Logged Function |
| <0x7f0b1c00[4101][com.example.fakecam]> | Session Object, PID, App ID |
ClientStartedSession:1 | Started Session flag |
| Cam/Audio:1/0 | Cam/Audio |
| bg:0 | Background |
| micStolen:0 cameraStolen:0 | Mic Stolen, Camera Stolen |
| sessionStatus:Running | Session Status |
The value
Who benefits. Examiners who see capture-session lines in a sysdiagnose and need to know whose they are, and researchers comparing what different iOS builds log.
Which question can now be answered. “Which process asked the capture daemon for a session, and when?” On the iOS 26.5.2 image the answer was a system daemon, and Camera Stolen read 1 on 6 rows, all lines of that daemon. The artifact copies the flag and does not say what it means.
What it changes in a case. An examiner can treat a session row as an indicator tied to a named process and a time, and place it beside the streaming and indicator rows. Without it, a reader sees only a count of lines.
Limits. A start line is the daemon's record of a request, not of frames recorded. Microphone capture that does not pass through this daemon is not collected. An empty result is not evidence that nothing was captured.
Show this figure as a table
| Kind of line | Outcome |
|---|---|
| Session create, start, stop, detach | Collected and listed |
| State line, started or stolen flag is 1 | Collected and listed |
| State line, all three flags 0 | Collected, not listed |
| Torch lines | Not collected |
| Device-vendor lines | Not collected |
| ISP subsystem lines | Not collected |
| Audio sessions, calls, voice recording | Not covered |
Where this sits in the Lab's research
This contribution supports Lab Technical Report LAB-TR-0012, “What the iOS Unified Log Says About Device Security Controls: Most Leave a Trace, Few Record a Change” (preprint, not peer reviewed; available on this site, Zenodo deposit pending, so there is no DOI yet). The report counts the camera and microphone indicators as a partial trace, lists capture sessions among its nine marker families, and uses the biometric daemon's sessions as a look-alike that a marker must not count as app capture (its Table 6 and Table 7). The report's point that most controls leave a trace and few record a change applies here: a session line is a trace of the daemon's request, not a record of a change in anything the user set.
The companion white paper LAB-TR-0017 (preprint on this site; Zenodo deposit pending) maps where security settings live on disk and does not cover capture sessions. No other Lab report covers this contribution; the method above is from the pull request.
Sources
Every number in this post is listed here with where it comes from. Figures use these numbers or invented example values, marked as such.
| Number or claim | Where it comes from |
|---|---|
| 3 artifacts; 18, 10 and 5 columns | Pull request diff: CAPTURE_SESSION_HEADERS, CAPTURE_STREAMING_HEADERS, RECORDING_INDICATOR_HEADERS |
| 316 lines added to logarchive.py; 1,656 lines added in 7 files; merged 20 October 2026 | Pull request metadata (files, additions, mergedAt) |
| 102 session rows, 78 with an app ID naming the daemon, 24 other rows; none from the Camera app or a third-party app | notes of logarchive_capture_sessions and sample_data in the diff; pull request body |
| 12 streaming rows (6 with streaming 1, 6 with 0) on iOS 26.5.2; 0 on iOS 26.6 | notes and sample_data of logarchive_capture_streaming in the diff |
| 1 indicator row on iOS 26.6; 0 on iOS 26.5.2, where 51 indicator-prefix lines were opacity or force-layer lines | notes and sample_data of logarchive_recording_indicator in the diff |
| Camera Stolen read 1 on 6 rows | notes of logarchive_capture_sessions; pull request body |
| 1,140 lines of the camera subsystem on iOS 26.6, none in these shapes | notes of logarchive_capture_sessions in the diff |
| 19 tests in four groups | Count of test functions in test_logarchive_capture_sessions.py in the diff |
| Torch lines outnumbered session lines in the iOS 27.0 log | notes of logarchive_capture_sessions in the diff (no count given) |
| Camera and microphone indicators as a partial trace; biometric sessions as a look-alike | LAB-TR-0012, Tables 4, 6 and 7 |