dInfinity

Security

dInfinity runs entirely on the phone. It has no backend, no accounts and no analytics, so most of the usual attack surface does not exist. What is left is the part that matters: the app installs content other people wrote.

Reporting a vulnerability

Report privately through GitHub’s security advisories — not as a public issue, and not as a pull request that fixes it, since either one publishes the problem before there is a release to upgrade to.

Please include what you did, what happened, and the smallest dice set, collection or file that reproduces it. You will get an acknowledgement, and a say in how the fix is described when it ships.

There is no bounty. This is a hobby project.

Supported versions

The latest release. Releases are immutable, so a fix ships as a new version rather than a re-tag of an old one.

What the app trusts, and what it does not

The threat model in one line: a dice set, table or saved-roll collection is data written by a stranger, and the app treats it that way.

Input Treated as
A downloaded dice set or table (docs/dice-sets.md) Hostile until validated
An imported saved-roll collection (docs/dice-notation.md) Hostile until validated
A formula typed by the user Untrusted length and shape; bounded by the notation limits
A photo chosen as a table background Untrusted image, decoded with explicit bounds

The defences, all documented in docs/dice-sets.md:

If you find an input that gets past any of these, that is exactly the kind of report this file is asking for.

Network

The app works offline. The only traffic it ever makes is a download the user asked for — a dice set, a table or a saved-roll collection — and those are https only: the manifest sets android:usesCleartextTraffic="false", so a set that names an http:// URL fails to download rather than fetching over a link anyone on the network can rewrite.

Data on the device

Statistics, saved rolls, history and installed sets stay on the phone. There is no telemetry and no upload. Cloud backup is switched off deliberately (app/src/main/res/xml/data_extraction_rules.xml); a device-to-device transfer may carry the data, so a new phone does not cost you your campaign’s history. Export is a manual action through the share sheet, and the export never includes the roll seeds.

android:allowBackup is deliberately left at its default rather than set to false. On Android 12 and above false only stops cloud backup — which data_extraction_rules.xml already stops, completely — while device-to-device migration cannot reliably be disabled at all. Android’s own guidance for an app with data worth protecting is to keep the attribute and restrict through the extraction rules, which is what this app does. Static analysis flags the default as worth a look; it has been looked at, and this is the answer.

Verifying a release

Every release APK is signed with the project’s release key. Its certificate fingerprint is public, and pinned in the repository at keystore/release-certificate.sha256:

ce1366a9471577ad7bf5423351a1c45f2c5a6bfcd9d99fef1cc6436a62d26f39

To check a downloaded APK yourself:

apksigner verify --print-certs dInfinityApp-<version>.apk

The release workflow checks the same thing before publishing, so an APK signed with the debug key or a regenerated one never reaches a release. Each release also carries the APK’s own SHA-256 alongside it.

Signing keys

Keystores and their passwords are never committed — see keystore/README.md. A leaked release key cannot be un-leaked, only rotated. Releases are signed with v3, which carries a proof-of-rotation record, so a compromised key can be replaced without stranding everyone who already installed the app — but rotation is still a last resort, not a plan. Release signing happens where the key lives; a checkout without the keystore still builds, it just produces unsigned release artifacts.

Automated checks

None of these replace review, but they catch the boring half:

Out of scope