Summary
Samsung phones keep a short text log of every start of their recovery program, with the options it was started with. This change makes ALEAPP read all the files that hold that log, show the options exactly as printed, and stop carrying a damaged text in the Build column.
It extends the existing recovery history artifact (sRecoveryhist) instead of adding a second one, and it adds one artifact, Bug Report - Recovery History, for the single case the first cannot see: a bug report zip that is itself a file inside an extraction. Times stay as printed. Nothing is judged: a row says recovery was started with the options shown, which is an indicator and not a finding that work was done.
- 3,055lines added, 100 removed
- 28files changed
- 5recovery files now read
- 8columns appended
The issue
An examiner has a Samsung extraction and a question that comes up often: was this phone reset, and what else did recovery do? The recovery history is the natural first look. It is a plain text file with one entry for each start of the recovery program: a header with a two-letter code, a date and the firmware, then the options recovery was started with, a status line and a reboot reason.
Before this change the artifact read one path, efs/recovery/history. Three things were out of its reach. The companion file extra_history holds step lines linked to an entry by the same two-letter code, and its format_volume lines name the volumes recovery formatted. The cache folder holds three more files, last_history, last_all_history and last_extra_history, and the same names turn up as members of a bug report. And the Build column was wrong for one header shape: when a header ends in a bracketed dash, the closing bracket and the dash were returned as part of the build text. That happened on 5 of 5 rows on one tested image and 0 of 3 on the other. The output looked ordinary, so nothing in it signalled a problem.
Show this figure as a table
| File | Before | After |
|---|---|---|
efs/recovery/history | read | read |
efs/recovery/extra_history | not read | read |
cache/recovery/last_history | not read | read |
cache/recovery/last_all_history | not read | read |
cache/recovery/last_extra_history | not read | read |
scripts/artifacts/sRecoveryhist.py (paths and notes) and pull request body, "What the artifacts read" (synthetic example).The practical effect is a quiet gap. An examiner who looked only at the main file could not see which volumes a wipe entry named, could not see options that have no column of their own (for example a data-resizing option), and had no way to know that a bug report kept on the phone carried another copy of the log. None of these is an error message. They are missing context, and missing context in a reset question is easy to read as “nothing more to see”.
The essence
The recovery history is now read from every place the tested devices keep it, and each row shows what the file says in the file’s own terms. The twelve earlier columns keep their order and form. Eight columns are appended before Source File, and the Build text no longer carries the trailing characters of a header that ends in a bracketed dash.
A row is one record of a history file. Activity names the options that are printed, for example Wipe requested (--wipe_data); it does not show that a wipe ran or finished. Times are shown as printed: Timestamp is the header date and Request Timestamp is the --requested_time value, with no other time derived and none labelled UTC.
| Item | What it reads or adds | Status |
|---|---|---|
| sRecoveryhist (Wipe & Setup) | efs/recovery/history with extra_history; cache/recovery/last_history, last_all_history, last_extra_history | Extended from one file to five |
| Bug Report - Recovery History | The same names as members of a bug report zip kept inside an extraction (input type fs) | New artifact |
| Activity, Options (as printed) | Words taken from the options; every -- line of the record | Appended |
| Formatted Volumes | The format_volume lines of the extra file that carry the same code | Appended |
| Status Line, Other Lines, Entry Notes, Entry Line, Extra History File | Status line of the record; lines in no other form; early header date note; line number of the header; the extra file used | Appended |
| Build | No longer carries the trailing ] [- | Fixed |
Our methodology
Format discovery. The method began with the files, not with the existing parser. The history files are plain text, and the Lab read them as they are: three header shapes occur (a bracketed header, a header that closes its first bracket after the tag, and an older shape without brackets), a record may end with a lone dash or with no separator, and the extra file uses lines of the form [CODE][S] text and [CODE][E] text. The reader keeps a record’s lines under the field they belong to and keeps anything it cannot place in Other Lines, so nothing is silently dropped. Control characters are shown as a space and a line is cut at 400 characters.
One reader for two artifacts. The reader has no dependency on the rest of ALEAPP. It joins each history file to the extra file in the same folder by code, and a code in the extra file with no record in the history file gets a row of its own, so volumes are never lost for want of a match. When a bug report zip is the input of the tool, its members are already loose files and sRecoveryhist reads them; the bug report artifact logs that it found only the report text. When the zip sits inside an extraction, its members are not files of the extraction and the bug report artifact reads them. That split is why no file is listed twice.
scripts/android_recovery_history.py, sRecoveryhist.py and bugreportRecovery.py in the diff (diagram).Synthetic fixtures. The pull request adds a fixture builder, a unit test file and ten case zips with baselines, all with invented values. They cover the shapes that real images did not supply: prompt-and-wipe records, the spaced reboot reason: form, header lines without pipes or build, a code used twice in one file, CRLF line ends, an extra file without a history file, a damaged zip, and wipe and region-change entries inside a bug report zip. The pull request lists these as constructed only; the other shapes were also run on real files.
Independent recount. On each real source the number of rows was compared with a separate count of the header lines of the file, made without ALEAPP code (Figure 3). The earlier columns were also compared field by field with the previous reading; they matched except for Build.
Show this figure as a table
| Source | Evidence class | Rows | Independent count |
|---|---|---|---|
| Samsung S-series, file system | full file system zip | 5 | 5 header lines |
| Samsung A-series, file system | full file system zip | 3 | 3 header lines |
| Samsung A07, bug report | bug report zip | 2 | 1 + 1 header lines |
| Samsung A26, bug report | bug report zip | 8 | 4 + 4 header lines |
| Samsung S-series, bug report | bug report zip | 38 | 19 + 19 header lines |
Missing data and limits. A zip with no recovery member gives no rows and the log says so; a zip that cannot be opened is logged and skipped; a member larger than 8 MiB is read up to that size. A header date before 2012 is noted on its row, because the tested wipe records carry 1970 header dates and one update-only phone carried 2010. Header dates were not always in ascending order, so row order is the order written and not a time order. The pull request also states what was not examined: vendors other than Samsung, Android before 16 for the added columns, a cache recovery folder with files in a file system extraction (empty on both images), and the recovery logs.
Checks. The pull request’s test record lists a lint score of 10.00 of 10, ten passing case runs, 1,034 unchanged bug report case runs, and the repository’s claim-language check. The artifact notes keep to what is printed and say plainly what is not sourced, for example what the S and E marks of the extra file stand for.
The value
For an examiner the change turns one list of entries into a readable trail. The question “which volumes did this wipe entry name?” now has an answer in the same row, and the question “does this phone hold a second copy of the log?” is answered by the Source File column. Because the cache files and the efs file can hold the same entries, one record can appear up to three times, each with its own source; the notes say so, so a repeated row is not read as three events.
The dates need care, and the change is built to support that care rather than replace it. Of the nine Samsung extractions with a history in the Lab’s report on reset indicators, the header clock was plausible on four; the others showed 1970, a constant wrong year, or another value that a plausibility test can pass. The artifact therefore prints dates as written and flags an early year. If a date is wanted for an entry on an unset clock, it is an estimate anchored to a recorded time, such as the requested time of a Settings wipe, checked with a pair of anchors, and reported in the time zone of that recorded time.
sRecoveryhist.py; reading rules from the artifact notes and LAB-TR-0004, Section 6.2 (synthetic example).What the change does not do is equally part of its value. It does not tell an examiner that a wipe happened. An entry with the wipe option is an indicator that recovery was started that way, consistent with a reset request. A device that shows no entry may simply not keep the file: in the Lab’s collection the file was absent on all three units of one flagship generation, which the report treats as unknown, not as evidence of no reset.
Where this sits in the Lab's research
The pull request is the reader side of the Lab’s study of reset indicators and their clocks: Addai, D. (2026). Informative but Individually Unreliable: An Empirical Study of Android Reset Indicators and Their Clocks. Lab Technical Report LAB-TR-0004, version 1.1, Zenodo, doi:10.5281/zenodo.23720065 (all versions: 10.5281/zenodo.23717926). Read the report on this site. The report measured 14 file system extractions and 7 bug reports, sorted the recovery header clock into five behaviours, and tested one candidate rule for dating undated entries that held on one phone and failed by 526 days on another. Its comparison of readers with the earlier artifacts is where the Build column error was found.
The recovery history is also one of the traces that mark wipe events in Lab Technical Report LAB-TR-0028 (available on this site as a preprint; Zenodo deposit pending), which lists it with its own clock as a known source of error. No Lab report covers the cache copies or the bug report artifact specifically; for those the method above is from the pull request.
Sources
Every number and claim above, with where it comes from.
| Number or claim | Source |
|---|---|
| Pull request merged 28 October 2026; 3,055 additions, 100 deletions, 28 files | Pull request metadata (gh pr view) |
| Files read: five recovery files; eight appended columns; twelve earlier columns | Pull request body "What a row is"; DATA_HEADERS in the diff of sRecoveryhist.py |
| Build wrong in 5 of 5 rows on one image, 0 of 3 on the other | Pull request body, "Fixed on the way" |
| Rows 5, 3, 2, 8, 38 with independent counts 5, 3, 1+1, 4+4, 19+19 | Pull request body, "Proven on real sources" |
| Four bug reports with no recovery member (Samsung A17, Huawei, Transsion-brand, Motorola) | Same table, last row |
| Ten case zips; 10 passes; 1,034 unchanged bug report case runs; lint 10.00 of 10 | Pull request body, "Test record" |
| Header dates 1970 on tested wipe records, 2010 on an update-only phone; early-year note before 2012 | Pull request body; HEADER_NOTE_YEAR = 2012 in android_recovery_history.py |
| Text cap 400 characters; member cap 8 MiB | MAX_TEXT and MEMBER_CAP in the diff |
| Same entries up to three times, one record per file | sRecoveryhist.py notes in the diff |
| Header clock plausible on 4 of 9 extractions with a history; 526 days; 14 extractions and 7 bug reports | LAB-TR-0004, Sections 1 and 5.3 and Table 3 |
| File absent on all three units of one flagship generation | LAB-TR-0004, Section 5.1 |
| Build text damaged in 5 of 5 rows (measured on one image) | LAB-TR-0004, Table 9 |
| Recovery history listed as a wipe-event trace with its own clock | LAB-TR-0028, Table 2 |
| Figures 1, 2 and 4 are diagrams with synthetic values; Figure 3 uses the counts above | Author |