Work-in-progress proposal. packslip is a draft, not an adopted standard: it is far from certain that mise or Omarchy will adopt it, and nothing depends on it yet. The format still changes between releases and the tooling is young. Expect breaking changes until it is declared stable.
One signed manifest per release.
Vendors publish it beside their artifacts, signed from CI with no key to manage. Consumers verify it against the repository name and get checksums, platforms, executables, and provenance links.
packslip showverified{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "mise-v2026.9.1-linux-x64.tar.xz",
"digest": { "sha256": "3f9a…c21e" } }
],
"predicateType": "https://packslip.dev/release/v1",
"predicate": {
"project": "github.com/jdx/mise",
"version": "2026.9.1",
"published_at": "2026-09-01T12:00:00Z",
"source": { "repo": "https://github.com/jdx/mise",
"commit": "a1b2…", "tag": "v2026.9.1" },
"artifacts": [
{ "name": "mise-v2026.9.1-linux-x64.tar.xz",
"os": "linux", "arch": "x86_64", "libc": "gnu",
"size": 12345678,
"url": "https://github.com/jdx/mise/releases/download/…",
"format": "tar.xz",
"bin": ["mise/bin/mise"],
"provenance": ["https://api.github.com/repos/jdx/mise/attestations/sha256:3f9a…"] }
],
"identity": {
"scheme": "sigstore-oidc",
"key_id": "https://github.com/jdx/mise/.github/workflows/release.yml@refs/tags/v2026.9.1",
"issuer": "https://token.actions.githubusercontent.com" },
"supersedes": "2026.9.0"
}
}
Every installer re-invents release verification, badly.
Checksums over TLS prove nothing
Most tools download a SHASUMS256.txt from the same place as the binary. If the release page is hijacked, the checksums are hijacked with it. The digest matches and the malware installs.
Registries make vendors do the work
Package managers keep a central repository of YAML describing how each vendor names assets, where its checksums live, and which file is the binary. Vendors have to add themselves, and every entry rots.
Build provenance says how, not what
SLSA provenance proves an artifact came out of a given build. It does not say which artifact is for which platform, what to put on PATH, or what else shipped in the release. That is the part a manifest has to carry.
The vendor describes the release. The forge vouches for the vendor.
The release job signs it
One step after the artifacts are built. It attests their build provenance, digests them, infers platforms from file names, and signs the manifest through sigstore with the workflow's own OIDC identity. No secret is created or stored.
- uses: jdx/packslip@v1
with:
artifacts: dist/*
bin: mise
One file ships beside the artifacts
packslip.sigstore.json becomes a release asset. It is a standard sigstore bundle: the statement, the certificate naming the workflow that signed, and the Rekor log entry proving when. cosign and gh attestation read it as-is.
The consumer checks the name
A packslip for github.com/jdx/mise must be signed by a workflow of that repository through GitHub's issuer. The project name is the pin. Nothing to configure on first use, nothing to look up in a registry.
packslip verify packslip.sigstore.json \
--artifact mise-v2026.9.1-linux-x64.tar.xz
packslip invents as little as possible. The document is an in-toto Statement v1 in a sigstore bundle, the shape GitHub artifact attestations, npm provenance, and Homebrew bottles already use. Identity comes from sigstore's certificate authority and transparency log. What packslip adds is the predicate: the release manifest a registry entry would otherwise hold.
The name is the location and the identity, like a Go module path.
github.com/jdx/mise
Releases and their packslips are GitHub release assets. The signer must be a workflow of that repository through GitHub's OIDC issuer. Same shape for gitlab.com/group/tool.
mise.jdx.dev
The vendor controls the domain and publishes a signed release list at /.well-known/packslip.json: the same bundle format, listing each release's packslip with its digest, an expiry, and a sequence number so a mirror cannot freeze or roll back what consumers see.
github.com/oxc-project/oxc/oxlint
A monorepo names each tool with a subpath, like a nested Go module. Each tool gets its own packslip and version per release, the real tag lives in source.tag, and the pin stays the repository. No more guessing which of three binaries a release meant, or how cli/v1.9.4 maps to a version.
The packslip proves the manifest. SLSA proves the build.
A verified packslip
The named signer published exactly this list of artifacts, with these digests and platforms, at a time the transparency log recorded. A hijacked release page cannot alter it, and a rolled-back or missing release is visible.
Linked provenance
Each artifact can link its SLSA build provenance. A consumer that verifies those statements earns the SLSA build level the builder establishes. GitHub-hosted runners with actions/attest-build-provenance, which the action runs for you, give Build L3.
No scale of its own
packslip defines no evidence levels. Consumers record what they verified as a SLSA verification summary or in their own terms, and enforce floors and no-downgrade on the scheme, the signer, and the presence of provenance.
Publishing is one step. Consuming is six rules.
For vendors on GitHub
Add the action to the job that publishes your release, after the artifacts exist.
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: jdx/packslip@v1
with:
artifacts: dist/*.tar.xz dist/*.zip
bin: mytool
That is the whole integration. The action attests build provenance for every artifact, writes and signs the packslip with the job's identity, links the provenance from it, verifies what it wrote, and uploads packslip.sigstore.json to the release. The project name defaults to github.com/<owner>/<repo> and the version to the tag.
Everywhere else
The same binary runs anywhere. Inside any CI with an OIDC token it signs keylessly. Otherwise generate a key once, keep the secret half in your secret store, and publish release.pub so consumers can pin it. The signature is still logged to Rekor.
cargo install packslip
packslip keygen -o release.key
packslip create \
--project mytool.example.com \
--version "$VERSION" \
--key release.key \
--out dist \
--url-base "https://dl.example.com/mytool/$VERSION" \
--bin mytool \
dist/*.tar.xz
packslip releases --project mytool.example.com \
--sequence 42 --valid-for 30d --key release.key \
--release "https://dl.example.com/mytool/$VERSION/packslip.sigstore.json=dist/packslip.sigstore.json" \
--out site/.well-known/packslip.json
Platforms are inferred from file names; write path:os/arch[/libc] to override one and @fips-style variants to tell two builds of one platform apart. --bin oxlint=bin/oxlint-x86_64 names an executable differently from its file. Add --provenance URL once per artifact, in order, if you publish build provenance yourself. A monorepo runs the step once per tool with --project github.com/owner/repo/<tool>.
For consumers
Whether you are a package manager, a distribution repository, or a corporate mirror, the same rules apply.
- Pin the identity once. For a forge project the name is the pin: accept only the forge's issuer and an identity under the repository. For a project on its own domain, pin its public key or identity. Never take a key from the document, and never trust a bundle's key hint.
- Verify the bundle, then the statement, then the subject digest and size of every artifact you downloaded.
- Enforce no-downgrade. Refuse a release whose scheme is weaker than the last accepted one, whose signer changed without a human saying so, or that dropped provenance the last release carried. Compare keyless signers by workflow path, not ref.
- Apply a minimum release age to the log's integration time, so a hijacked release is noticed before unattended installs pick it up.
- Treat
supersedesas the ordering hint for rollback detection. Never parse version strings. - Order by the vendor's word, never by parsing. Every project has a release list: GitHub's releases endpoint, or a signed list on the vendor's domain.
version_ordersays whether that list's order or semver ranks releases. Refuse a stale signed list, never select a yanked release, skip prereleases unless asked. - Never guess between two artifacts. Select by os, arch, libc, format, and variant; if two still match, stop.
packslip verify packslip.sigstore.json \
--artifact mise-v2026.9.1-linux-x64.tar.xz --json
{
"project": "github.com/jdx/mise",
"version": "2026.9.1",
"published_at": "2026-09-01T12:00:00Z",
"scheme": "sigstore-oidc",
"key_id": "https://github.com/jdx/mise/.github/workflows/release.yml@refs/tags/v2026.9.1",
"issuer": "https://token.actions.githubusercontent.com",
"logged_at": "2026-09-01T12:00:41Z",
"provenance_linked": true,
"checked_artifacts": ["mise-v2026.9.1-linux-x64.tar.xz"],
"artifact_count": 6
}
Tighten the pin with --identity-prefix or an exact --identity and --issuer; pin a key with --pubkey. The verifier is also a Rust library, and packslip schema prints the JSON schema for other languages.
Built for the tools that install other people's binaries.
omapac and the Omarchy Package Repository
Vendor-built packages are generated from the vendor's packslip instead of hand-edited checksum lines. The repository re-verifies against the project's identity, enforces a minimum release age and no-downgrade, and ships the packslip as a sidecar so clients can chain repository signature to vendor identity offline.
mise
mise's own release workflow is intended to be the first external publisher and the reference. Its github and http backends will verify a packslip when one is present and record what was verified in mise.lock, so a tool with a packslip needs no registry entry.
Everyone else
The name is neutral on purpose and the format carries no distro or vendor specifics. aqua, Homebrew, a corporate mirror, or a one-off install script can all verify the same file against the same name.