How we check files
Every app on 2113 Apps is free and open-source software, and every file is an official build. Most apps come from one of two repositories. IzzyOnDroid publishes the APKs that developers build and sign themselves. F-Droid builds most of its apps from their published source code and signs them with its own keys; a few are signed by their developers. Some apps, including many of the emulators and launchers in Games, come straight from each project’s GitHub releases. Each app page says which source its file came from. Before we host a copy, it has to pass the checks below. A file that fails any of them is not published, and an existing page is not updated to it.
Files from IzzyOnDroid and F-Droid
1. The repository index is signed
Both repositories sign their index with a repository certificate. We check that signature against a certificate fingerprint fixed in our code: for IzzyOnDroid, the one it publishes on its website; for F-Droid, its long-standing repository certificate, which is also the key that signs the F-Droid app itself. We don’t simply trust whatever key we are given the first time. Only after that check passes do we read anything else from the index.
2. The file is exactly the one the index describes
The signed index lists a SHA-256 hash for every APK. We download the file and compare its hash byte for byte. The hash is shown on each app page, so you can check your own download against it.
3. It is signed by the right key
We read the signing certificates out of the APK (v1, v2 and v3 signature schemes) and require the one the index lists for that app. An APK re-signed by anyone else fails here. The fingerprint is on each app page as “Signer SHA-256”. Because F-Droid usually signs with its own key, an app from F-Droid can’t be updated with a copy of the same app from Google Play or GitHub, or the other way round, without uninstalling first.
4. The manifest matches
The package name and version code inside the APK must be the ones the index says they are.
5. Which version we host
We host the newest version the repository offers as a regular release. Versions it marks as beta are listed on the app page but not hosted. When a repository builds an app separately for each type of processor, we take the build that runs on the most phones: a universal build if there is one, otherwise the 64-bit ARM one.
Rarely, we skip a version: when the developers say not to use a release that the repository still offers, we keep the release before it until a newer one comes out. The app page then marks the skipped version as not hosted and says why.
Files from GitHub releases
GitHub has no signed index, so this chain is one step weaker, and each page says which source its file came from.
1. The official release
We take the file from the latest full release (not a pre-release) on the project’s own GitHub repository. The page links to that exact release.
2. The file matches GitHub’s hash
For files uploaded since mid-2025, GitHub publishes a SHA-256 hash for every release file. We compare our download against it byte for byte. Older uploads have no published hash; for those we check the file size only, and the page doesn’t show the “matches GitHub’s published hash” badge.
3. The signer never changes silently
The first time we check an app, we record its signing certificate. Every later release must be signed by the same one. If it changes, the update is held until we have checked that the key change is genuine, for example announced by the project. This catches a file replaced after that first check. It can’t catch a repository that was already compromised when we first looked, which is one reason we only list established projects with a public release history.
4. The manifest matches
The package name inside the APK must be the one we list.
For every file: no public test keys
Android’s source code includes a few sample signing keys, and because their private halves are public, anyone can sign an app with them. An APK signed with one of these keys fails, whatever its source.
Older versions
For a few apps that people look for by version number, such as Magisk, Termux and KernelSU, we also host the last few releases. Each older file goes through the same checks as the current one: its hash must match the signed index (or, on GitHub, the published hash where one exists; older GitHub uploads are checked by file size), and it must be signed with the same key as the current version. Releases signed with a different key are left out, because they could neither update nor be updated by the version you have. For F-Droid apps, versions that have left F-Droid’s main repository come from its archive repository, whose index is signed with the same F-Droid certificate. We don’t repeat the tracker scan for older versions; the permissions and trackers on each app page describe the current version.
What we measure
Once a file passes, we open it up and record what it contains:
- Permissions declared in the manifest, grouped by how Android grants them.
- Native code: which CPU types the build includes.
- Supported Android versions from the minimum and target SDK.
- Tracker code: every class name in the app’s code is compared with the tracker signatures published by Exodus Privacy. A match counts only when that class is actually in the package.
- Capabilities such as the Shizuku API, the Xposed API or root-shell libraries. Our topic lists use these: an app is only listed under “Shizuku apps” if the Shizuku library is actually in its APK.
Signatures that match too much
A few Exodus signatures are broad enough to match open-source libraries that aren’t trackers. We list each exception and say so on the app’s page. We don’t drop matches silently.
- ByteHook (bhook), matched by the Pangle signature: ByteDance’s open-source PLT hook library (MIT, github.com/bytedance/bhook). Exodus’s Pangle signature matches every com.bytedance class, so it catches this library too. Only classes under com.bytedance.android.bytehook. are excluded; any other code from the same company would still be reported.
Libraries that are only referenced
An app’s code can mention a library by name without including it, for instance an optional library it checks for while running and uses only if it is there. When a tracker signature matches nothing but such mentions, none of the tracker’s own code is in the file, so we don’t count it as a tracker. We still note the match on the app’s page.
What these checks found across every app we host, with the raw data, is in our open-source APK report.
What these checks don’t tell you
- They show that a file is the developer’s own, unmodified release. They don’t show that the developer’s code is safe or bug-free.
- Finding a tracker SDK means its code is in the package, not that it runs or what it sends. Not finding one means none of the known signatures matched. Custom or renamed code can still collect data.
- We don’t run the apps. Everything above comes from reading the file, not from watching it work.