Current release: v0.4.0
- AMT Action Journal (new opt-in feature) — new
amt_action_eventtable (Room v7 → v8, strictly additiveCREATE TABLE+ 3CREATE INDEX IF NOT EXISTS). Records every destructive / state-changing action AMT itself dispatches (uninstall, force-stop, disable/enable, clear cache/data, move-to-trash, restore, quarantine HARD/SOFT). Distinct boundary fromapp_lifecycle_eventwhich logs OS broadcasts from ANY installer ; this surface answers "what did AMT do on my behalf" for forensic post-mortems. - Privacy model: opt-in default OFF, retention 30–365 days
(clamped on read AND write), Room private DB excluded from cloud
backup. Never leaves the device, no INTERNET permission, no GMS,
no proprietary dependency. The
AmtActionLoggershims every call site through a singleAtomicBooleangate fed by adistinctUntilChangedcollector — zero DataStore IPC at hot path when the journal is OFF. - Defence-in-depth on the journal write path — validation
isValidPackageName()at both theRecordAmtActionUseCaseentrance AND theAmtActionRepositoryImpl.insertentrance (double-gate).toDomainOrNullmapping silently drops rows whose enum values the running build does not recognise (downgrade tolerance ; the row stays on disk for a future re-upgrade — never silently mutated). Append-only by design : the DAO exposes onlyinsert,purgeOlderThan,count,deleteAll— no UPDATE path whatsoever, audit trail stays monotonic. - Defence-in-depth on the worker scheduling —
AmtActionJournalPurgeWorkermirrorsLifecyclePurgeWorkerexactly : 2-minwithTimeoutcap,if (!enabled) Result.success()guard re-read from DataStore on every tick (so a stale scheduled instance cannot leak rows after the user opts out).applyAmtActionJournalScheduling(enabled = false)cancels the unique work outright. Cold-start re-sync fromMainApplication.syncBackgroundWorkersrecovers from device reboot / OEM aggressive killer. - Audit fixes shipped together (v0.3.4 deferred + v0.4.0 audit) :
HashUtils.ktextracted (core/ext/) — single source of truth for SHA-256 digest helpers (cert fingerprint format + content-hash format + APK streamed file hash). Two prior private implementations now delegate ; the APK policy (canonical resolve + install-root whitelist + 500 MB cap) stays at the call site.@IoDispatcherinjected across 9 ViewModels + 2 data-layer classes that previously usedDispatchers.IOhardcoded (StorageVM, ExpertVM, AppDetailVM, AppListVM, RarelyUsedVM, ZombiesVM, SmartCleanerVM, TrashRepositoryImpl, ApkBackupManager). ZeroDispatchers.IOhardcoded remain in the codebase — uniform testability + dispatcher discipline.- Every
hasUsageStatsAccess()AppOps IPC call is now off the main thread (4 ViewModels migrated from synchronous constructor call →viewModelScope.launch { withContext(io) { … } }). No more jank potential on slow OEM AppOps binder. FilterChipparity across every filter-row surface (LifecycleHistory + PermissionDrift WindowPickers switched from semantically-wrongAssistChip).- Smart Cleaner + Security Audit uninstall paths now feed the Action Journal (audit D2 — coverage was incomplete).
applyLifecyclePurgeSchedulingadded tosyncBackgroundWorkerscold-start path (audit D3 — symmetry with the new action journal scheduling).
- No new permission, no new dependency, no INTERNET, no GMS.
- Room schema v7 → v8 (single ALTER + 3 indices, strict additive).
- Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- Multi-tag refactor (DataStore wire format) —
AppSettings.appTagswidens fromMap<String, AppTag>toMap<String, Set<AppTag>>. Encoded as astringSetPreferencesKeyofpkg=TAG1|TAG2|…entries (the|separator is invalid inside anAppTag.name, so the encoding is unambiguous). Decoder is tolerant + defence-in-depth : malformed entries are silently dropped, unknown enum names are skipped, the per-entry tag arity is capped atMAX_TAG_ARITY_PER_ENTRY = 8(a tampered DataStore feedingpkg=WORK|WORK|…|WORK× 10 000 is bounded), and thepkgkey is validated viaisValidPackageName()for read-path symmetry with the existingsetAppTags(pkg, Set)write-path. Backward-compatible with the v0.3.3 single-tag format (a suffix without|deserialises as a one-element Set), so an upgrade preserves every existing tag. - APK SHA-256 forensics on Lifecycle events — new
apk_sha256column onapp_lifecycle_event(Room v7, ALTER TABLE ADD COLUMN TEXT NULLable).RecordLifecycleEventUseCase.computeApkSha256reads the base APK atApplicationInfo.sourceDir, hashes it withMessageDigest.getInstance("SHA-256")streamed at 64 KB, and stores the hex digest on INSTALLED / REPLACED / BASELINE rows. UNINSTALLED rows leave the hash NULL (the APK is gone by the time the broadcast fires). Defence-in-depth on every failure path :canonicalFileresolves symlinks BEFORE the read so a hostile link is caught by the prefix check rather than being followed.- Whitelist of canonical install roots (
/data/app/,/system/app/,/system/priv-app/,/product/app/,/vendor/app/) — stock AOSPsourceDiris trustworthy, but root/MOD ROMs can lie; the prefix check fences off the worst cases (/dev/urandomvia symlink). - 500 MB hard cap on the APK size we hash — above this we log +
skip the hash rather than monopolise the IO pool for tens of
seconds. The lifecycle row still inserts with
apkSha256 = nullso the audit trail is never lost. - IO / Security exceptions swallowed + Timber-logged (no PII in
the log message — only the canonical path).
Tamper detection lives entirely in the UI layer (
computeTamperedIds, pure O(n log n)remember(events)) — no extra Room query, no background work, no IPC.
- Tag filter on AppList —
AppListViewModelinjects the existingSettingsRepository(no new permission, no new IPC). Filter is a pure in-memory post-filter on the existinggetInstalledAppsflow ; chip toggles do NOT re-subscribe to the heavy Room query. - TagPickerDialog double-toggle hardening —
Checkboxnow passesonCheckedChange = nullso the parentRow.toggleableis the single source of click handling.role = Role.Checkboxset on the Row for TalkBack semantics. Prevents the double-toggle no-op that could surface on some Compose versions. - No new permission, no new dependency, no INTERNET, no GMS.
- Room schema v6 → v7 (single ALTER TABLE ADD COLUMN, NULLable).
- Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- Signature clusters : reuses the existing
getSignatureSha256(pkg)PM call already shipped since v0.1.0 — no new permission, no new IPC surface. Renders read-only — tapping an app drills down to AppDetail (no destructive action accessible from the new screen). - Custom user tags : new
AppSettings.appTags Map<pkg, AppTag>persisted in DataStore as aSet<String>ofpkg=ENUM_NAMEentries. Decoding is tolerant — malformed / unknown entries silently dropped. Tags are device-local only ; no network, no GMS. - No new permission, no new dependency, no INTERNET, no GMS.
- Room schema unchanged (still v6).
- Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- OS hibernation read —
UsageStatsManager.isAppInactive(pkg)(API 23+). Requires PACKAGE_USAGE_STATS (already declared since v0.1.0 for the storage / usage features). Defensive against SecurityException and IllegalArgumentException — both fall back tofalserather than poisoning the scan. - Permission delta on REPLACED events —
DetectPermissionDeltaUseCasecompares the granted-dangerous-perms snapshot of aREPLACEDlifecycle event against the closest preceding baseline (BASELINE/INSTALLED/ earlier REPLACED). Surfaces supply-chain creep signal in the LifecycleHistoryScreen row. Pure domain — no Android dep, no new I/O. - Lifecycle journal in PDF — capped to 200 entries (defensive PDF
size cap). The full history stays in-app via LifecycleHistoryScreen.
Same SAF destination as the v0.2.2 PDF export — no
MANAGE_EXTERNAL_STORAGE. - Room schema v6 — strict-additive
ALTER TABLE ADD COLUMN is_hibernated INTEGER NOT NULL DEFAULT 0. Pre-existing rows read0(false) via DEFAULT; the next scan repopulates the real OS state.MigrationTest_v5_v6ships. - No new permission, no new dependency, no INTERNET, no GMS.
- Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- Safety Guardrails extended — the hold-3s
CriticalWarningDialognow also gatesdisable(AppDetail),uninstallNow+emptyTrash(Trash), anduninstall(SmartCleaner). Same threat model as v0.2.0 Phase A (uninstall + quarantine HARD) — protection against accidental destructive action on a 2FA / banking / password-manager app. - User-customisable whitelist —
safety.userProtectedPackagesset persisted in DataStore + reactiveAtomicReferencecache inCriticalAppDetectorsoclassify(packageName)stays synchronous on the ViewModel hot path. Capped at 200 entries (defensive — same intent as the ignore-list cap). ProtectedAppsScreen+ companion picker — read-only sections per built-in CriticalCategory (full transparency: the user sees exactly which apps AMT ships hardcoded) + editable user-added list with explicit-tap removal (no swipe, dialog confirmation).- No new permission, no new dependency. The picker reuses
appInfoRepository.observeApps(includeSystemApps=false)— the same authorised reads as the rest of the app. - Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- PackageMonitor BroadcastReceiver — runtime-registered (NOT manifest),
gated by
lifecycle.enabled. Idempotent register/unregister viaAtomicBoolean. The receiver is registered withRECEIVER_NOT_EXPORTED(broadcasts are system-only on Android 8+, the flag is required on API 33+). Defence-in-depth:isValidPackageName()validation of the broadcast'sdata.schemeSpecificPartbefore any DB write. - Append-only Room v5 log —
app_lifecycle_eventtable. Insert is the only mutation, exceptsetReasonForId(id, reason)which carries an explicit narrowWHERE id = :idto keep the audit trail monotonic. - No data egress — no new INTERNET permission, no GMS, no FCM. The
Lifecycle History DB lives in the app's private files dir and is excluded
from cloud backup via the existing
backup_rules.xml. - Retention — clamped
[30, 365]days in the picker; theLifecyclePurgeWorkercancels itself when the master toggle is OFF so no background work survives the feature being disabled. - Self-exclusion — the receiver ignores events for App Manager Tech itself (self-updates would otherwise pollute the timeline).
- Cert SHA-256 stable (
76:E8:77...60FF1CF) since v0.1.0.
- Mode Expert: read-only inspection of internal components
(activities, services, receivers, providers +
exportedflag, declared permissions + grant state + dangerous-protection flag, signature SHA-256- debug-keystore detection via X.509 Subject DN, UID, SDK envelope, ABI,
APK paths / splits, curated AppOps probed via
AppOpsManager.unsafeCheckOpNoThrow). Strictly read-only — no PackageManager mutation, no app-op rewrite, no component enable/disable (those require ADB, Shizuku or root and are intentionally out of scope for an F-Droid release).
- debug-keystore detection via X.509 Subject DN, UID, SDK envelope, ABI,
APK paths / splits, curated AppOps probed via
- PDF diagnostic export: built via
android.graphics.pdf.PdfDocument(native AOSP, API 19+), 0 external dependency. Output stream is the user-picked SAF Uri — noMANAGE_EXTERNAL_STORAGE. Report contents never include APK Manager's own runtime state, only what we read from the OS surface (PackageManager,StorageStatsManager,UsageStatsManager,DevicePolicyManager,AccessibilityManager). - No new permission declared in the manifest. No new dependency added.
- Same threat-model envelope as v0.2.1.
| Field | Value |
|---|---|
| Algorithm | RSA 4096 / SHA384withRSA |
| Alias | app_manager_tech |
| Validity | 100 years from 2026-05-23 |
| SHA-256 | 76:E8:77:2E:09:95:13:69:40:5F:58:E7:0C:4A:FF:FD:41:C4:68:75:53:C6:CF:A0:3D:08:14:5F:F6:0F:F1:CF |
| SHA-1 | 3B:93:44:DC:7E:72:47:92:FD:31:CA:24:EB:38:8C:C0:34:63:BA:0E |
This fingerprint must remain stable across releases — verify before publishing
each new GitHub release that the SHA-256 matches the previous one.
F-Droid's reproducible-build pipeline pins to this fingerprint via the
AllowedAPKSigningKeys field of the metadata yml.
App Manager Tech reads installed package metadata, storage statistics and usage timestamps from the Android OS. It does not transmit any data off-device — no INTERNET permission is declared, the OS itself enforces this.
Primary threats and mitigations:
| Threat | Mitigation |
|---|---|
| Malicious caller feeding forged package names | isValidPackageName() guard in every use case that accepts a packageName from outside |
| Privilege escalation via PackageManager | Only public PackageManager / StorageStatsManager / UsageStatsManager APIs; no shell, no root, no reflection on hidden APIs |
| Data leakage via screenshots / recents | FLAG_SECURE opt-in setting (default OFF — user controls) |
| Backup exfiltration | DataStore preferences + Room DB excluded from cloud backup via backup_rules.xml + dataExtractionRules |
| Tracker SDK supply-chain | Zero Google Mobile Services, zero analytics, zero crash reporter — every dependency is Apache 2.0 / MIT / BSD open source |
| Cert pinning bypass | N/A — no network requests are made (no INTERNET permission) |
| Tampering with the soft-delete Trash | MoveAppToTrashUseCase wraps Room writes in try/catch → typed AppError.DatabaseError ; upsert is idempotent (REPLACE) so double-tap is safe |
| v0.2.0 — Permission Drift snapshot tampering | Read-only access to PackageInfo.requestedPermissionsFlags via PackageManager; no write path to other apps' permission state; Room writes append-only on internal DB |
| v0.2.0 — Quarantine APK backup exfiltration | User explicitly picks the backup folder via SAF OpenDocumentTree (persistable grant) — backup lives outside the app's private dir but in user-owned storage; filename whitelisted to [a-zA-Z0-9._-] (no path traversal); MIME pinned to application/vnd.android.package-archive |
| v0.2.0 — Accidental destructive action on critical app | CriticalAppDetector whitelist-classifies authenticators / banking / password managers / health / E2E messaging / transport apps; CriticalWarningDialog requires a 3-second hold-to-confirm with drag-cancel (touchSlop), aligned with SMS Tech EmergencyHoldButton pattern |
| v0.2.0 — Quarantine HARD data wipe surprise | Pre-confirm dialog explicitly states "App data will be permanently lost on uninstall" with the confirm button repainted in BrandDanger; SOFT mode (reminder only, no destruction) is the default selection |
None. App Manager Tech performs no cryptographic operations: no encrypted storage (the Room DB is private app-data, protected by Android sandboxing), no PIN-lock, no message signing, no network = no TLS.
If a future release adds a PIN-locked view, it will use Android Keystore + Tink AEAD (AES-256-GCM). No home-grown crypto.
| Permission | Type | Reason |
|---|---|---|
QUERY_ALL_PACKAGES |
install-time (normal) | Enumerate every installed app via PackageManager |
PACKAGE_USAGE_STATS |
granted by user in OS Settings | Last-used timestamps via UsageStatsManager + cache / data sizes via StorageStatsManager |
REQUEST_DELETE_PACKAGES |
install-time (normal) | Launch the OS uninstall flow; the OS asks for user confirmation per package |
KILL_BACKGROUND_PROCESSES |
install-time (normal) | Best-effort force-stop of background processes |
RECEIVE_BOOT_COMPLETED |
install-time (normal) | Reschedule the background-scan WorkManager job after device reboot |
POST_NOTIFICATIONS |
runtime (Android 13+) | Display the single cache-threshold notification |
INTERNET |
not declared | Strictly local app — Android enforces no socket can be opened |
MANAGE_EXTERNAL_STORAGE |
not declared | Out of scope; everything goes through SAF (Storage Access Framework) |
| Version | Date | Scope | Findings |
|---|---|---|---|
| v0.2.1 | 2026-05-24 | FULL-APP "peigne fin" audit (3-axes + cohérence transversale) post-v0.2.0 + 6 user-reported UX bugs (Trash ghost rows, SecurityAudit refresh stuck, Zombies row tap, Storage refresh, AppDetail "last used", Settings shortcut Outils) | 0 CRITICAL, 8 HIGH (H1 SystemClock vs wall-clock in hold-3s timer, H3 SettingsScreen missing PermissionDrift+Quarantine callbacks, C2a/b + C8a/b RarelyUsed+Zombies missing UsageStats banner+ON_RESUME, C7a/b/c/d 4 withTimeout sites missing) + 12 MEDIUM (C1a/b/c/d AtomicBoolean replacing racy guards, M1 IntentFactory require validPkg, M2 notif ID negative hashCode mask, M4 AppDetail load fan-out timeout, M5 SecurityAuditScreen BrandDanger, M-1 delta hasUsageStatsAccess off-main, M-4/L-2 Box weight, C3a Trackers ScanDone snackbar, C6a BatchAction filter) + 12 LOW — all blocking findings fixed before tag. CI fix: CodeQL --no-build-cache --rerun-tasks so tracer observes compilation. |
| v0.2.0 | 2026-05-23 | v0.2.0 delta — Permission Drift Tracker + App Quarantine (HARD APK backup + SOFT reminder) + Safety Guardrails (CriticalAppDetector + CriticalWarningDialog hold-3s) + Room v3→v4 migration + new IntentFactory.appPermissionsSettingsChain + SAF backup folder |
0 CRITICAL, 1 HIGH (USER_PROTECTED enum phantom — fixed by adding empty Set + ranking entry), 4 MEDIUM (notif hash collision 0x7FFF → full 32-bit, restore HARD ConfirmDialog → DestructiveDialog, detectTapGestures no drag-cancel → pointerInput awaitPointerEventScope with touchSlop, label resolution cap deferred v0.2.1) + 5 LOW — all blocking findings fixed before tag |
| v0.1.0 | 2026-05-23 | Phase X final — Trash feature (Room v3 migration + 3-way uninstall dialog) + batch confirmation dialogs + 3 list-by-criterion screens (Rarely-used / Zombies / Permission-filter) + brand-discipline red | 0 CRITICAL, 1 HIGH (MigrationTest_2_3 missing — fixed), 6 MEDIUM (try/catch dao.upsert, fillMaxWidth(0f) invisible label, Spacer.padding anti-pattern, 3 IconButton contentDescription, dead strings settings_trash_default_*, UninstallChoiceDialog M3-deviation doc) + 4 LOW — all fixed before tag |
| v0.1.0 | 2026-05-23 | Phases I→IX — scaffold + Room v2 + Storage analyser + WorkManager + Smart Cleaner + Trackers scan + Transparency screen + i18n FR+EN + signed release infra | All audit findings resolved per phase (see git history) |
- System-partition modification
- Root / ADB operations
- Network access (no internet permission, not planned)
- Access to other apps' private data directories
Security disclosure: contact@files-tech.com
Please include:
- Android version and device model
- App version (shown in Settings > About)
- Reproducible steps
- Perceived impact
We target a 72-hour initial response for credible vulnerability reports.