Allow F-Droid maintainers to quickly disable broken releases

Nitai Sasson
2025-09-27T00:26:18Z

Two recent events prompted me to write this:

1. K-9 Mail: fdroid/fdroiddata#3631 / https://floss.social/@fdroidorg/115156301676175134
2. ntfy: fdroid/fdroiddata#3646

When F-Droid discovers it's serving an update that is actively harmful to users, **it should have a way to react quickly and minimize the number of users affected.** Tooting isn't enough.

I have thought of a way to allow such quick reactions without having to re-sign the index and without opening up big new vulnerabilities.

I only have a surface-level understanding of the F-Droid API model, so if something doesn't work the way I assume, I'd appreciate an explanation or link.

This is the general idea:

## Index patches are hosted in a different URL
For example, `f-droid.org/repo/index-v2.json` which would contain a list of modifications to the packages in the index:
```
{
"index-timestamp": 1758810829000,
"actions": [
{
"action": "remove_version",
"package": "io.heckel.ntfy",
"version": "cf7e8c47cfdd99a2391945564095fc26d0e916c71a028fc60b150357621fe225",
"reason": "Unintended release, crashes on start"
},
{
"action": "remove_package",
"package": "some.innocentlooking.app",
"reason": "App turned out to be entirely malicious, oops!"
},
{
"action": "remove_version",
"package": "some.other.app",
"version": "a1...",
"reason": "Critical vulnerabilities"
},
{
"action": "remove_version",
"package": "some.other.app",
"version": "b2...",
"reason": "Critical vulnerabilities"
}
]
}
```

This file is signed by **a new key pair** entrusted to an F-Droid maintainer. I'll call this the "patcher key".

In this example:

- `index-timestamp` refers to the timestamp of the `index-v2.json` for which these patches are intended. The client will only honor these instructions if the timestamp matches.
- `io.heckel.ntfy` has its offending version removed
- `some.innocentlooking.app` is removed completely because it was malicious
- `some.other.app` has two of its versions removed. It is assumed that the index still contains at least one version that's still safe to install. This is included only to demonstrate that two modifications can be applied to a single package.

**The allowed actions are `remove_package` and `remove_version`.** It's not possible, for example, to add new versions or packages - that would have to be in the actual index update.

Other actions might be possible, like `add_anti_feature` or similar metadata changes, but these are never going to be as urgent as `remove_package` and `remove_version` so to keep things simple I think only these two actions should be allowed.

The patcher key should be kept safe, but not to the same extent as the key used to sign the index. Its power is limited, so it can be stored in a personal machine and be internet-connected. If the key leaks, damage is very minimal and the key can be de-authorized in the next build cycle. The upshot is you can minimize harm to users in a moment's notice.

## Change to the index
The index is still generated with the same security as before. A new item would be added to authorize the patcher key:

```
{
"repo": {
[...]
"patches": {
"name": "/index-v2-patches.json",
"key": "(patcher public key)",
"permissions": ["remove_package", "remove_version"]
}
}
[...]
}
```

It still remains unchanged between build cycles.

I hope such a change is backwards-compatible with older F-Droid clients that don't recognize that item. If not, it should be fairly easy to think of another way to authorize it.

## Client applies patches
The F-Droid client will pull the index as before. If the index includes the `patches` item, the client also pulls the patches file.

The client verifies that the patches are signed with the authorized key, and the actions are allowed by the permissions. The client then applies the actions to the index: it will not install the removed versions or packages, and it will not show them in the interface (or it will show them greyed out and show the reason for this). Users with an updated client are protected from installing an affected package.

## Possible issues
It's fairly likely that I have some fundamental misunderstanding about how the F-Droid website is hosted. I think any roadblocks like that can be worked around if you let me know what they are. I'm also confident you can come up with solutions yourselves, if you see value in this concept.

## Developing this
If you show interest, I can try to develop this for a merge request, but this is probably a bad idea. I've never developed for Android in my life, I'm not familiar with any part of the codebase, I don't fully understand your security model and signing process... It's just a huge effort for me to get this done. It would be a great learning experience for me, but it might end up taking much longer than it should and with sub-par code quality (simply due to my inexperience with the relevant libraries, APIs and languages).

I'm still probably up for it, if you think this is a good idea but no one capable can invest the time in it. Let's just start with a conversation though. Is this a good idea in the first place?