Make §45's logging rules true in a release build, and guard them #38
Labels
No Label
P0
P1
P2
release-blocker
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: null/Privacy-Period-Tracker#38
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The app does not log health data today. Nothing stops it starting.
What is true now
A grep for
Log.,printlnand Timber acrossapp,coreanddomainfinds two hits, neither of them health data:PeriodApplication.kt:48sets WorkManager's minimum logging level, and a comment inReminderWorker.ktexplains why aLog.dthere would be a bad idea. So the rule currently holds by good behaviour rather than by construction.The Batch 06 milestone lands only when "the release build has no cycle date reachable in any log or crash payload".
What it costs
A single
Log.d("cycle", record.toString())added while debugging a defect writes menstrual dates to logcat, where any app with log access on older Android versions, any bug report, and any crash reporter can read them. It is one line, it is invisible in review, and it is exactly the failure §45 is written about.What to do, in order
docs/architecture/GUARDS.mdrequires this, andscripts/prove-guard.shperforms it. Three guards in this project have been green over the exact failure they claimed to catch; assume a new one is broken until watched failing.android.util.Logorprintlnreachable from the modules that see cycle data, and anytoString()on a domain type that would render a date into a log.Traps
data classtoString()is the leak.PeriodRecord(startDate=2026-08-18)is a cycle date in a log, and nobody wrote a logging statement to produce it — an exception message or a crash payload will do it. Consider overridingtoString()on the domain types that carry dates.docs/security/SECURITY.md— this guard is what makes adopting one safe later, so it is worth having before the decision rather than after../gradlew check, which is what the hooks and any CI actually run.Why filed and not fixed
It is real work with a proof step, not a tidy-up, and the R8 half cannot be checked without building and inspecting a release artifact.
Verify:
./gradlew checkfails when a health-data log call is injected into a module that sees cycle data, and passes when it is removed — demonstrated withscripts/prove-guard.sh; and a release APK, driven through logging a period and receiving a reminder, produces no cycle date in logcat.Shipped in
a1efbde. checkNoHealthLogging in the root build.gradle.kts fails ./gradlew check on any logging call in a module that can see a cycle date; proved with scripts/prove-guard.sh — an injected Log.d in CycleRepository gave exactly one failure, and a comment containing Log.d(/println( stayed green, as does the android.util.Log.WARN constant in PeriodApplication. R8 -assumenosideeffects strips android.util.Log from release. Beyond the issue: Prediction's require messages were interpolating five predicted dates into an IllegalArgumentException, which §45 forbids in crash reports and no source guard could see — fixed, and PeriodRecord/SpottingRecord/CycleRecord/Prediction/NotYetObservation now override toString() so a data class cannot render dates into any string that touches it, pinned by NoDatesInDiagnosticsTest (7 cases, itself proved to fail). Verified on a real minified release APK signed with the debug key and driven from onboarding through logging a period: zero ISO dates and zero health words in logcat. 201 tests pass.