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

ThreatOutcome
A developer’s token is stolenThe thief can list repository metadata. Nothing else.
A CI registry token is stolenThe 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 compromisedPublished 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 compromisedOut 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:

AppInstalled onPermissions
ReaderRepositories that own cratesMetadata read; organisation members read
StorageThe storage repository onlyContents 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.