Open Source / iLEAPP #2577

Lockdown Mode, passkey, shortcut and VPN log lines

Most iOS security controls leave a trace in the unified log, and few record a change. This contribution reads four of those traces, each as a restated status, and says in plain words what a line does not establish.

  • Daniel Addai, SectraTEK Lab
  • Merged
  • CC BY 4.0

View the pull request on GitHub

Summary

The pull request adds four small artifacts to iLEAPP's logarchive module. Each reads one literal line from the unified log: the Lockdown Mode flag that Photos prints, the number of passkey records Safari's saved account store loaded, telemetry signposts of the background shortcut runner, and the VPN connectivity state word. Each description says what a line establishes and what it does not.

The four share a pattern that runs through the Lab's research: most controls leave a trace, and few record a change. On the research logs the Lockdown Mode flag read NO on every line, and a VPN state line never named a VPN. A row is an indicator, and it does not prove that a control was on, used or changed.

  • 4artifacts added
  • 166lines of parser code
  • 7tests on a synthetic export
  • 12counts re-derived by separate code

Methodology note

Drafted with AI assistance and reviewed by the author before publication.

The issue

An examiner is asked whether Lockdown Mode was on, whether a VPN was in use, whether a shortcut ran in the background, or whether passkeys were loaded. A search of the unified log for “lockdown” looks promising and misleads. It finds an unrelated service, a line from the telephony side that prints <private> in place of the value, and, in one place, a line that prints the value (Figure 1). That last line is the Photos check for Shared Albums. It prints isLockdownMode: followed by YES or NO each time the check runs.

The line is a status, not an event. In the research logs it read NO every time: 1, 2 and 8 lines on three captures, and 0 lines on five public-corpus images. No log from a device with the mode turned on was available, so what the YES form looks like is unverified. A NO line says the mode was off when the line was written. It says nothing about the hours between lines, and a day without a line says nothing about the feature, because a line exists only when the code that prints it ran.

The same care applies to the other three lines. A passkey count is not a list of passkeys. A shortcut signpost is not proof that a shortcut finished. A Disconnected VPN line names no VPN.

Three kinds of line that mention lockdownThree schematic log lines. A line from the lockdownd service is not matched because the service is unrelated. A callservicesd line prints private in place of the value and is not matched. A Photos line prints isLockdownMode with a value and is matched.A search for lockdown finds three kinds of line (schematic)lockdownd (a different service)any lineNot matchedAn unrelated servicecallservicesdlockdownModeEnabled: <private>Not matchedThe value is withheldPhotos (Shared Albums check)... isLockdownMode: NOMatchedThe value is printedSchematic lines, not quotations. Only the third kind is read by the artifact.
Show this figure as a table
Kinds of line
LineOutcome
lockdownd serviceNot matched, unrelated service
callservicesd lockdownModeEnabledNot matched, value is private
Photos isLockdownModeMatched, value printed
Figure 1Why the marker is a subsystem plus literal text: the word lockdown alone finds an unrelated service and a line with no value. Source: notes of logarchive_lockdown_mode in the pull request diff. Schematic lines (synthetic example).

The essence

Four thin artifacts read the collected table like the existing ones. Each marker combines a subsystem with literal text, and each row is a log line with its standard columns. Nothing is interpreted. The table lists what each one saw on the research logs.

The four artifacts and what was observed
ArtifactObserved on the three main capturesObserved on public-corpus images
logarchive_lockdown_mode1, 2 and 8 lines, all NO0 lines on all five
logarchive_passkey_store4, 8 and 27 lines; several processes wrote them4 lines on the iOS 26.6 image, none on four others
logarchive_shortcut_actions0, 0 and 6 lines; two actions, 5 lines for one and 1 for the other4 lines on the iOS 26.5.2 image
logarchive_vpn_state55, 17 and 17 lines723 lines on iOS 26.5.2 (Connected 718, Disconnected 5); 41 on iOS 26.6, all Disconnected

Markers that no log supported were left out: Stolen Device Protection, NFC and Apple Pay events, and foreground Shortcuts runs had none in the available logs.

Our methodology

Finding the markers. Each marker was taken from decoded sysdiagnose logs as literal text inside a named subsystem (Figure 2), and added to the collection step as well as to the artifact, so the rows reach the table the artifact reads.

Checking the counts twice. The tool was run on the three sysdiagnoses with the real ileapp.py, which imported about 25 to 72 million log records per archive. Separate code, with no iLEAPP in it, ran the same decoder on the same archives and applied the marker conditions to its output. It gave the same twelve numbers and the same VPN split (Figure 3). The numbers in sample_data and the notes were re-derived from the finished runs.

A correction made on the way. The first draft of the VPN notes said every line was Disconnected. The third capture showed Connecting and Disconnecting lines, and the notes were corrected. The repository's output checker also listed columns that held one value on every row, such as subsystem, and an empty category for the VPN artifact. Each is named in the notes, so a reader does not mistake a constant column for a finding.

Synthetic tests and scope. Seven tests run the chain on a synthetic export and check that each artifact returns exactly the expected lines and that every pattern it reads is collected. No test case file was added, because the artifacts read a table the case harness does not build. What was not verified is stated in the pull request: the YES Lockdown form, the Connected VPN state on these captures, foreground Shortcuts runs, other iOS versions, and logs reached by full file system extraction.

Four markers and what a row meansFour rows, one per artifact. Each shows the subsystem and literal text marker, what a row says, and what a row does not say: Lockdown flag, passkey record loads, background shortcut actions and VPN connectivity state.Marker (subsystem, text)A row saysA row does not saycom.apple.photos.backendisLockdownMode:The value Photos printedwhen it checked Shared AlbumsA change, or that the modewas never on at another timecom.apple.mobilesafaripasskey keychain records fromA count the store printedwhen it loaded recordsA list of passkeys, a siteor an accountcom.apple.shortcutsRunningLifecycle: actionID=The runner wrote a telemetrysignpost naming an actionWhat triggered it, or thatthe action completedcom.apple.networkextensionVPN connectivity state is nowThe state word as logged(Disconnected, Connecting ...)Which VPN, provider orserver was involved
Show this figure as a table
Markers and limits
ArtifactA row saysA row does not say
Lockdown Mode flagThe value Photos printed when it checked Shared AlbumsA change, or that the mode was never on at another time
Passkey record loadsA count the store printed when it loaded recordsA list of passkeys, a site or an account
Background shortcut actionsA telemetry signpost naming an actionWhat triggered it, or that it completed
VPN connectivity stateThe state word as loggedWhich VPN, provider or server
Figure 2Each artifact carries its own limits in its description and notes. The markers are literal text inside a named subsystem. Source: descriptions, notes and collection query in the pull request diff. Diagram.
Matching lines on three capturesBar chart on one scale from 0 to 60, three captures per artifact. Lockdown flag 1, 2 and 8, all NO. Passkey loads 4, 8 and 27. Shortcut actions 0, 0 and 6. VPN state 55, 17 and 17.Matching lines per artifact on three captures (A, B and C)0102030405060Lockdown flag, A1 (NO)Lockdown flag, B2 (NO)Lockdown flag, C8 (NO)Passkey loads, A4Passkey loads, B8Passkey loads, C27Shortcut actions, A0Shortcut actions, B0Shortcut actions, C6VPN state, A55VPN state, B17VPN state, C17A and B: iOS 26.6, one iPhone, A taken a day earlier. C: iOS 26.6.2, a second iPhone.
Show this figure as a table
Lines per artifact and capture
ArtifactA (iOS 26.6, earlier)B (iOS 26.6)C (iOS 26.6.2)
Lockdown Mode flag (all NO)128
Passkey record loads4827
Background shortcut actions006
VPN connectivity state551717
Figure 3Counts are small, and they differ between captures of the same device. A separate script re-derived all twelve. Source: pull request body and the sample_data and notes in the diff. Real counts; no synthetic values.

The value

Who benefits. Examiners who need a time-stamped line about one of these controls, and researchers comparing what a build prints.

Which question can now be answered. “What did the log print about this control, and when?” The answer is a last known value with a validity window: valid from the first to the last line that restates it, unknown outside the log's window, and a change only somewhere in the gap before the next value (Figure 4). A tunnel that logs Connecting and Disconnecting lines shows more than a device that logs only Disconnected, and neither names a VPN.

What it changes in a case. It stops two mistakes. One is reading a restated status as an event. The other is reading an absent line as an absent control. Where an examiner needs the setting itself, a file in the same extraction may hold it, and the Lab's file-side map says how.

Limits. Only the direct tracev3 import was tested. A VPN line was seen as Connected on a public-corpus image whose device had a VPN configured, but not on the three main captures. Counts depend on how the device was used and on the log window.

Reading a status line over timeTimeline with six dots for six status lines: three Disconnected close together, then Connecting, Disconnecting and Disconnected. The value is valid between first and last restating line. A change lies in the gap between the last Disconnected line and the first Connecting line, and the state is unknown beyond the last line.A state is a last known value with a validity window (invented sequence)Value restated: valid from first to last lineDisconnectedConnectingDisconnectingDisconnectedA change lies somewhere in this gap; no line records itUnknown beyond the last lineend of logInvented sequence. Real captures held 36 Disconnected, 9 Connecting and 10 Disconnecting lines in one log.
Show this figure as a table
Points on the timeline
PointMeaning
Three Disconnected linesValue restated; valid from first to last line
GapA change lies here; no line records it
Connecting, Disconnecting, DisconnectedLater values, each valid only between its own lines
After the last lineUnknown
Figure 4What a restated status supports: a value valid between its first and last line, and a change only somewhere in the gap before the next value. Source: reading rule in LAB-TR-0012 Section 6.1; the line sequence is a synthetic example, the 36, 9 and 10 are from the pull request notes.

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 includes these four families in Table 6 with the same counts. It records Lockdown Mode as only ever restated as off, notes that the VPN state never read Connected in the three iOS 26 sysdiagnoses while its iOS 27.0 log held 1,121 lines with 5 Connected, and states the reading rule behind Figure 4 in its Section 6.1.

The white paper LAB-TR-0017 (preprint on this site; Zenodo deposit pending) is the file-side companion: it found the Lockdown Mode key in 5 of 33 global preference files and says a reader should report “key absent”, not “off”. LAB-TR-0013 (preprint on this site; Zenodo deposit pending) measured a related point for messages: the log named about a quarter of recent messages, so a missing line says little. It does not concern these controls.

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.

Sources for every number and claim in this post
Number or claimWhere it comes from
4 artifacts; 166 lines added to logarchive.py; 7 tests; merged 25 October 2026Pull request metadata and diff
Lockdown flag 1, 2, 8 (all NO); passkey 4, 8, 27; shortcut 0, 0, 6; VPN 55, 17, 17; VPN split 36, 9, 10Pull request body, test checklist
Same twelve numbers from separate code; about 25 to 72 million records per archive; pinned decoder 0.7.0Pull request body, test checklist and test record
Captures: iOS 26.6 twice (one device, a day apart), iOS 26.6.2 (second device)Pull request body; notes in the diff
Corpus images: Lockdown 0 rows on five; passkey 4 rows; shortcut 4 rows; VPN 723 (718 Connected, 5 Disconnected) and 41sample_data and notes in the diff
Passkey rows by process; two shortcut actions (5 and 1 lines)notes of logarchive_passkey_store and logarchive_shortcut_actions in the diff
First VPN notes said all Disconnected; correctedPull request body, checklist
Not verified: YES Lockdown form, Connected VPN, foreground Shortcuts, other versions, full file system logsPull request body
Table 6 counts; Lockdown only restated off; VPN 1,121 lines with 5 Connected; reading ruleLAB-TR-0012, Table 6, Section 5.2 and Section 6.1
Lockdown key in 5 of 33 global preference files; report key absent, not offLAB-TR-0017, Table 7 (pitfall P7) and abstract
Log named about a quarter of recent messagesLAB-TR-0013, abstract (24% of 2,867 messages)