Add & trust a repo
Add a repository by URL plus a pinned Ed25519 public key delivered out of band — never trust-on-first-use. Every repo you enable is verified independently and its apps stay labelled by source.
Depot — the app-distribution model
Depot is Postern's app-distribution model — a signed-catalog, multi-repository client built the way F-Droid proved this should work, and reimplemented from zero so nothing here depends on fdroidserver's AGPL code. Instead of trusting one Postern-run list of apps, you keep a list of repositories: ours, plus any community's, company's, or independent developer's you choose to add. Each one is added by URL and a pinned Ed25519 public key delivered out of band — never by showing up and being trusted on first contact. Every repo is verified on its own terms, every card tells you which repo it came from, and the strongest badge Depot hands out — ATTESTED — has to be earned by an actual rebuild from source, not claimed.
How it fits together
A Depot repo is a signed index file: app metadata kept separate from its versions and builds, a self-describing repo{} block, and a detached Ed25519 signature over the exact bytes served — the same separation F-Droid's index-v2 format proved out. The client fetches that index, verifies the signature in-browser via WebCrypto, and merges apps from every repo you've enabled into one list, always labelled by source. We reused the concept, not the code: no line of fdroidserver (AGPL) is in this stack, and everything is reimplemented under a permissive license so Postern's own stack stays permissive end to end.
Layer 1 · Repo
Any operator — Postern Store or a third party. Just a static file host and an offline Ed25519 keypair; no coordination with Postern required.
Layer 2 · Signed index
catalog.json + detached .sig, a self-describing repo{} block, format postern.catalog/v1 extended — index-v2-like, no fdroidserver code reused.
Layer 3 · Client (Depot)
Verifies the signature in-browser via WebCrypto, applies the normalizeEntry honesty rules, merges apps from every enabled repo, and always shows the origin repo on the card.
Plainly stated: no fdroidserver (AGPL) code is used anywhere in this stack — only the index-format concept, reimplemented from zero under a permissive license.
Same rules, any repo
Publishing a Depot repo takes a static file host and an offline Ed25519 keypair — nothing else. A developer, a community, or a company can put their own catalog up and have their users add it exactly the way they'd add ours: URL plus pinned key. What doesn't move is the honesty layer: normalizeEntry, the code that decides what a listing is allowed to claim, runs identically over every repo, first-party or not.
normalizeEntry honesty rules apply: a proprietary app can never show "hosted by us"; ATTESTED requires the attestable-nix tier, a PASS record, and a pinned builder.normalizeEntry honesty rules apply: no repo can grant itself a stronger badge by writing different JSON.What Depot actually does
Add a repository by URL plus a pinned Ed25519 public key delivered out of band — never trust-on-first-use. Every repo you enable is verified independently and its apps stay labelled by source.
A signed, version-monotonic revocations.json covers compromised repo keys, compromised builder identities, and malicious artifact hashes, checked against a freshness window (max_stale_secs, 14 days by default).
The ATTESTED badge only counts a PASS record if it's signed by a builder identity the client already pinned as trusted — never one the record names for itself. Closes the forge-a-PASS-record attack.
Two independent build hosts rebuild an app from its pinned source commit in a hermetic Nix sandbox and compare the result to the published artifact. Only an explicit PASS on both comparisons unlocks the badge.
Metadata separated from versions/builds, a self-describing repo{} block, a detached signature over the exact bytes served — the index-v2 shape, reimplemented from zero with no AGPL code reused.
Any static file host plus an offline Ed25519 keypair is enough to publish a repo — no coordination with Postern required, and the same honesty rules apply automatically.
normalizeEntry enforces the non-negotiable rules in code, on every repo: no proprietary app shown as "hosted by us," no checksum shown unless signing.state is actually published, no self-granted badges.
The strongest badge
The ATTESTED badge means: this app was rebuilt from its exact, pinned source commit, in a hermetic Nix sandbox, on two independent build hosts; both rebuilds matched each other and matched the binary actually published; and the record proving all that was signed by a builder identity the client had already pinned as trusted — not one the record names for itself. That last part matters: without it, anyone publishing a build could forge a PASS record and vouch for a key they generated five minutes earlier. Depot's builder-trust check closes exactly that hole. What it does not prove: that the source code is safe, or that Postern (or anyone) has audited it. Reproducibility answers "is the binary the source?" — nothing more.
Trust can be withdrawn
revocations.json is a signed, monotonically-versioned revocation list covering three things: repo keys found to be compromised, builder identities compromised or decommissioned, and individual artifact hashes discovered — after publication — to be malicious. The client checks this list against a freshness window (max_stale_secs, 14 days by default) and, when a fresh check fails, shows that in the UI rather than defaulting to a quiet "still trusted." Be clear about the limit: a well-positioned network attacker can suppress the revocation list until that window lapses. That's a real gap, not a hidden one — Depot's revocation is honest about being delayed, not instant.
Inside a CRL
revocations.json actually revokes.| Field | Revokes | Client behavior on match |
|---|---|---|
| Repo key | A compromised repo signing key. | That repo drops to UNTRUSTED and all of its apps are hidden. |
| Builder identity | A compromised or decommissioned builder key. | The ATTESTED badge is stripped from anything it signed; the tier falls back to whatever the catalog itself claims. |
| Artifact hash | A specific build later found malicious. | That exact version is flagged and blocked from install, even if its signature still checks out. |
version (monotonic) + max_stale_secs | The staleness of the CRL fetch itself. | The client refuses to treat a stale fetch as "no revocations" — it surfaces the staleness instead of hiding it. |
Terms, defined once
revocations.json — a signed, versioned list of revoked repo keys, builder identities, and artifact hashes, checked against a freshness window (max_stale_secs).catalog.json.sig), computed over the exact bytes served, verified in-browser via WebCrypto.normalizeEntry / honesty spinesigning.state is published.What's built, what's next
The signed-catalog verification, multi-repo add-by-key, the CRL schema and freshness check, and the builder-trust pinning logic all exist in code today, as a reference implementation — unaudited, like everything else on this site. What doesn't exist yet: automatic key rotation (that's TUF, and it's the acknowledged next step), and a populated catalog of ATTESTED, per-app builds — right now the mechanism is proven and the shelves are close to empty. We'd rather ship the honest trust plumbing first and fill the catalog after than backfill the trust story once apps are already listed.
max_stale_secs (14 days by default) expires — a real gap, disclosed, not hidden.