Security model
GitHub is the root of trust. PrivateCrates is designed so that stealing our keys, or taking over the whole service, does not let anyone change a published crate without your verifier noticing.
The trust boundary
- What we see: callers’ tokens, and crate bytes in transit while they are published.
- What we store durably: nothing. Caches only, keyed by hashes of tokens, never tokens.
- What we can do: write to each organisation’s storage repository, through the storage App.
Threats and outcomes
| Threat | Outcome |
|---|---|
| A developer’s token is stolen | The thief can list repository metadata. Nothing else. |
| A CI registry token is stolen | The thief can read your crates for under an hour. We can revoke every outstanding token at once by rotating the signing key. |
| Our storage App key is stolen, or PrivateCrates is compromised | Published bytes cannot be changed: releases are immutable. A malicious new version has no valid provenance, and a changed owners file or rewritten index line is not an App-authored append; the verifier reports all three. On a crate that allows manual publishing, a forged version looks like a manual publish, which is why manual publishing is off by default and the verifier lists every one for review. Deleting releases breaks downloads, and is reported. |
| GitHub itself is compromised | Out of scope: GitHub is the root of trust by design. |
Permissions are GitHub’s
A caller may do what GitHub allows them to do on the repository that owns a crate: read it to use the crate, push to it to publish or yank. Two GitHub Apps keep our own access narrow:
| App | Installed on | Permissions |
|---|---|---|
| Reader | Repositories that own crates | Metadata read; organisation members read |
| Storage | The storage repository only | Contents read and write |
Neither App has repository administration, so neither can turn immutable releases off. Removing someone from your organisation or a team reaches us by webhook and cuts off their access within seconds. A crate someone cannot read is a 404, never a 403, so its existence is not revealed.
Controls
- Tokens are never logged, never stored and never sent anywhere but GitHub’s API.
- Narrow tokens by default: developers hold reader App tokens (metadata read); CI holds one-hour registry tokens with no GitHub access at all.
- Keys in a KMS: both Apps’ private keys and the key that signs registry tokens live in a cloud key management service. The service asks it to sign; the keys never exist in our environment.
- Transport: HTTPS only, with HSTS.
- Limits: capped request sizes, a publish rate limit per token, and timeouts on every call to GitHub.
Integrity and provenance
- Immutable releases: once published, a release’s tag and files can never be modified or deleted, whoever holds our keys.
- Checksums at publish: we compute the SHA-256 ourselves and compare it with the digest GitHub computes for the uploaded file before the release is published.
- Provenance: a CI publish stores the bound OIDC token with the release. It is signed by GitHub, not by us, and names the repository, workflow, crate, version and checksum.
- Append-only index: we only ever append lines or flip
yanked, and refuse any other rewrite. - Every write is a commit in your repository, made by the storage App so GitHub marks it verified, with the publisher’s GitHub login in the message.
- The verifier checks all of the above in your CI. Set it up.
Source code
The credential provider and the verifier are open source under MIT or Apache-2.0. The hosted service is source-available under the Business Source License 1.1: anyone may read and audit it, and self-host it for their own organisation. Each version converts to Apache-2.0 four years after release.