Summary
The pull request adds one artifact, logarchive_urlopens, to the logarchive module of iLEAPP. It lists three kinds of unified log line: SpringBoard dispatching a URL-open action, a UIKit scene receiving a URL, and the launch service reporting that no app is registered for a URL scheme. Each row is one log line, not one open.
iOS usually replaces the URL with <private>. The artifact therefore answers “was a link opened, when, and which process wrote the line”. It does not answer “which link”. A maintainer review found that the first version would return an empty report in every real run; the fix and the notes that state what a row means are described below.
- 3log markers read
- 54lines added, in 1 file
- 4research images in the notes
- 1review round that blocked the merge
Methodology note
Drafted with AI assistance and reviewed by the author before publication.
The issue
An examiner is asked whether a phone opened a link at about a certain time, for example from a widget or from another app. The unified log does hold traces of that: SpringBoard writes a URL-open action, a UIKit scene reports that it received a URL, and the launch service complains when no app handles a scheme. Before this contribution iLEAPP had no artifact for these lines. The examiner had to export the log and search it by hand.
The lines are also hard to read. iOS replaces the value of a dynamic argument with <private> unless the developer marks it public, and in most of these lines the URL is one of those arguments. A line says that something was opened. It rarely says what. One open can also write several lines, so counting lines overstates the opens.
The first version of the contribution had a second problem, found in review, and it is a useful one to know about. iLEAPP's dependent logarchive_* artifacts do not read the full log table. They read a smaller table that a collection step fills from its own list of markers. The first version listed the three markers only in the artifact's filter, so the collection step never kept those lines and the artifact returned no rows, without an error. The maintainer built a few lines to show it: the same filter returned all three lines from the full log table and none from the table the artifact reads. The review also suspected that the first counts in the description came from querying the full table and asked for a run of iLEAPP itself.
Show this figure as a table
| Time | Process | Marker | URL value |
|---|---|---|---|
| 10:02:11.120 | SpringBoard | UIOpenURLAction | private |
| 10:02:11.201 | MobileNotes | openURLContexts | private |
| 10:05:40.002 | lsd | no registered handler for URL scheme | private |
The essence
The artifact reads the unified log lines that match one of three literal markers and copies the standard log columns. It adds no URL and no inference. Its notes say, for each marker, what a line shows and what it leaves out. Lines that ask whether a scheme could be opened (-canOpenURL:) are checks, not opens, and are deliberately not matched.
| Marker | Typically written by | What a row says |
|---|---|---|
UIOpenURLAction | SpringBoard, and the app that receives the action | A URL-open action was sent or received. Most lines hold no URL. |
openURLContexts | An app or settings process (Preferences on the iOS 17.2.1 image), from the UIKit scene method | A scene was handed a URL. The value was <private> on the tested images. The name is broad and may match lines that are not opens on other images. |
no registered handler for URL scheme | The launch service (lsd) | A scheme had no registered app. The line names neither the requesting process nor whether the request was an open or a check. |
The Process Image Path column is the process that wrote the line, not always the app that received the URL. On the iOS 26.6 image SpringBoard wrote 4 of the 5 rows, and the receiving app is named inside the message.
Our methodology
Finding the markers. The three strings were taken from decoded sysdiagnose logs, as literal message text. A subsystem name alone was not used, because a name says which component wrote a line and not what the line records. The artifact follows the existing dependent pattern: no file paths of its own, a requirement to run logarchive first, and a SQL fragment that filters the collected table (Figure 2).
Checking against real logs. The description first reported a count on two research sysdiagnoses. The maintainer's review is the reason the method reads as it does now. The collection step was fixed, and the final notes record what the artifact returned on four research images (Figure 3): URLs appear unevenly, one message ended at 1,029 characters with no closing bracket, so a longer line would be cut in the parsed log, and the other action lines held no URL at all.
Saying only what was tested. The review asked that the redaction claim be limited to the tested images, that the notes say a row is a line and not an open, and that someone look at what the broad openURLContexts marker also picks up. The final notes do the first two, and record that one image gave no matching line. They state that the marker can match non-opens on other images.
Behavior when data is absent. If no line matches, the report has no rows. The notes say this plainly, and the empty iOS 17.3 result in Figure 3 is kept in the sample data so a reader can see it.
Show this figure as a table
| Stage | What it does |
|---|---|
| logarchive module | Imports every log record into table logarchive |
| logarchive_artifacts() | Copies lines that match any marker into table logarchive_artifacts; the three URL-open markers were added here after review |
| logarchive_urlopens | Filters logarchive_artifacts with the same three markers |
| Report | One row per log line |
Show this figure as a table
| Image | Rows |
|---|---|
| iOS 14.3 | 1 |
| iOS 17.2.1 | 2 |
| iOS 17.3 | 0 |
| iOS 26.6 | 5 |
The value
Who benefits. Examiners who need to place a link open in time without reading raw log text, and researchers who want to know which processes write these lines on which iOS versions.
Which question can now be answered. “Did something on this phone try to open a link at about this time, and which process wrote the line?” The answer is an indicator. The notes advise correlating a row with other evidence, such as Safari history or network destinations, to resolve the target.
What it changes in a case. A time-stamped entry that did not exist in the report before. A widget launch, for example, now shows up as a few rows within a fraction of a second (Figure 4), and an examiner who knows that will not report it as three separate link opens.
Limits. A row is a log line. It does not prove that a person tapped a link, that a page loaded, or that an app acted on the link. The URL is usually missing, and the redaction claim holds only for the images tested. The log has a limited window, so an empty result does not show that nothing was opened. The artifact was tested through a sysdiagnose; the notes do not claim the same behavior for other ways of reaching the log.
Show this figure as a table
| Row | Line |
|---|---|
| 1 | Widget launch request carrying a UIOpenURLAction |
| 2 | SpringBoard sends UIOpenURLAction to the Notes scene |
| 3 | MobileNotes receives it |
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” (Addai, Tetteh, Adjabeng and Egyir, 2026; preprint, not peer reviewed; available on this site, Zenodo deposit pending, so there is no DOI yet). In the report, URL opens are the first of nine marker families (Section 3.4), and what the trace withholds and what it keeps is listed in its Table 5. The report's main finding is that most controls leave a trace and few record a change. A URL open fits that pattern: the line records that an open took place and which process wrote it, and not the target.
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 |
|---|---|
| Three markers read by the artifact | Pull request diff, scripts/artifacts/logarchive.py, function logarchive_urlopens() and logarchive_artifacts() |
| 54 lines added, 1 file changed, merged 14 October 2026 | Pull request metadata (files, additions, mergedAt) |
| Rows per research image: 1 (iOS 14.3), 2 (17.2.1), 0 (17.3), 5 (26.6) | sample_data of logarchive_urlopens in the diff |
| Three lines within 0.16 seconds; SpringBoard wrote 4 of 5 rows; message ended at 1,029 characters | notes of logarchive_urlopens in the diff |
| First version returned no rows; filter returned 3 of 3 constructed lines on the full table and none on the collected table | Review comment by the maintainer, state CHANGES_REQUESTED |
| Four commits; the last merged main and renamed and rewrote the artifact | Pull request commit list (the last title is truncated in the list) |
| Lines for -canOpenURL: are not matched | Pull request body and notes in the diff |
| URL opens as the first of nine marker families; what a URL-open trace withholds and keeps | LAB-TR-0012, Section 3.4 and Table 5 |
| Numbers 88 (iOS 26.6) and 24 (iOS 26.2) in the first description and in LAB-TR-0012 Table 6 | Not used for artifact output in this post: the maintainer suspected they were counts of the full log table, and the final notes give other counts |