privacy: scrub identifiers inside raw shell output
Running the Shizuku tier for the first time uploaded every MAC address on the local network to the server at the balanced level - fourteen of them, router and all. The probes embed raw command output verbatim (ip neigh, ip route), which is good evidence and also a complete household device inventory, and the anonymizer could not see it: classification is by field name and whole-value shape, and ip_neigh is one long string that is itself neither a MAC nor an address. measurement-schema.md flagged raw dumps as hard to anonymize and proposed dropping them from exports. Scrubbing is better: identifiers inside unclassified strings are replaced in place with the same pseudonyms used elsewhere, so a MAC appearing in both a parsed field and a raw dump still reads as one device, and the dump stays readable - neighbour-table shape, host count, RFC1918 addresses and vendor prefixes all survive. Dropping it would have protected the same data by destroying the reason for collecting it. One pass, not three: sequential passes re-process their own output. Once a MAC became 78:9a:18:xx:yy:zz the IPv6 pattern matched it - six hex groups separated by colons is an address - and destroyed the vendor prefix the MAC rule had just preserved. Ordered alternation resolves each position once, MAC first. RealDocumentTest runs the anonymizer over a captured run when ECHOLOT_REAL_RUN points at one and fails on any surviving MAC; it self-skips otherwise so no one's network lands in the repo. Against the document that leaked: 14 in, 0 out. Also: the Settings preview button did nothing, reading UiState.history which is empty until the History screen has been opened - same root cause as the "0 run(s)" count. It reads the archive now, and says when there is nothing to show. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d04babff51
commit
c19f382640
@@ -101,11 +101,10 @@ class MainActivity : ComponentActivity() {
|
||||
onApplyRetention = vm::applyRetention,
|
||||
onDeleteAll = vm::deleteAllRuns,
|
||||
onPreviewUpload = {
|
||||
// Preview the newest run, since that is the one the user just made
|
||||
// and the one they are deciding about.
|
||||
vm.state.history.firstOrNull()?.let { r ->
|
||||
lifecycleScope.launch { preview = vm.uploadPreview(r.id) }
|
||||
}
|
||||
// Straight from the archive: the newest run is the one the user just
|
||||
// made and the one they are deciding about. Always shows something,
|
||||
// even when there is nothing to preview yet.
|
||||
lifecycleScope.launch { preview = vm.previewNewestRun() }
|
||||
},
|
||||
onCheckServer = vm::checkServer,
|
||||
onEnroll = vm::enroll,
|
||||
|
||||
@@ -194,6 +194,21 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
store.read(id)?.let { store.redactedForUpload(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview of the most recent run, read from the archive rather than from [UiState.history].
|
||||
*
|
||||
* The history list is only populated once the History screen has been opened, so a preview
|
||||
* driven from it did nothing at all on a freshly-opened Settings screen — a button that
|
||||
* silently does nothing is worse than one that says why.
|
||||
*/
|
||||
suspend fun previewNewestRun(): String = withContext(Dispatchers.IO) {
|
||||
val newest = store.list().firstOrNull()
|
||||
?: return@withContext "No archived runs yet. Run a measurement first, then this will " +
|
||||
"show exactly what an upload would send."
|
||||
store.read(newest.id)?.let { store.redactedForUpload(it) }
|
||||
?: "That run could not be read back from the archive."
|
||||
}
|
||||
|
||||
fun archivedBytes(): Long = store.totalBytes()
|
||||
|
||||
/** Counted from the archive itself, not from [UiState.history], which is empty until the
|
||||
|
||||
Reference in New Issue
Block a user