Replacing curl | sh with a distribution-packaged verifier
#151
jdx
announced in
Announcements
Replies: 1 comment 7 replies
|
cc @suzuki-shunsuke, would love any feedback. I think aqua has the same issue with https://aquaproj.github.io/docs/products/aqua-installer#shell-script which this could resolve |
7 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR: Tools like mise, rustup, uv, and pnpm usually tell new users to run
curl | sh. Packslip offers a safer bootstrap: install one small verifier from your distribution's package manager, then let it fetch, verify, and install the upstream tool you actually want. mise already publishes signed packslip releases, so you can bootstrap it this way today. Any publisher that ships a packslip can use the same path.The problem
A
curl | shinstaller trusts whatever the download host serves. HTTPS proves you reached the host; it doesn't prove the script came from the publisher. A compromised host or CDN can substitute code over a perfectly valid TLS connection.Distribution packages avoid this, but their release schedules rarely keep pace with fast-moving tools. Package managers in particular often need to be current to work with their own registries.
The idea: two layers of verification
packslipwithaptordnf, signed by a package key you check once.packslip installchecks the upstream release's signature, signer identity, project, version, digest, and size before installing anything.Packslip itself is a release-manifest format, not another runtime manager. A publisher signs a statement listing each release artifact, its digest, its platform, and the commands it provides. The format is stable at version 1.
What a manifest looks like
This is the unsigned in-toto statement inside a packslip. The published file is a Sigstore bundle whose DSSE envelope signs this statement. The project, URL, and digest are placeholders.
{ "_type": "https://in-toto.io/Statement/v1", "subject": [{ "name": "exampletool-1.2.3-linux-x86_64.tar.gz", "digest": { "sha256": "<64 lowercase hex placeholder>" } }], "predicateType": "https://packslip.dev/release/v1", "predicate": { "project": "exampletool.example.com", "version": "1.2.3", "published_at": "2026-01-01T00:00:00Z", "identity": { "scheme": "sigstore-oidc", "key_id": "<signing identity placeholder>", "issuer": "https://token.actions.githubusercontent.com" }, "artifacts": [{ "name": "exampletool-1.2.3-linux-x86_64.tar.gz", "url": "https://downloads.example.invalid/exampletool-1.2.3-linux-x86_64.tar.gz", "size": 1234567, "format": "tar.gz", "os": "linux", "arch": "x86_64", "libc": "gnu", "bin": ["bin/exampletool"] }] } }Try it
With the packslip APT repository configured (the distribution guide has the setup commands, including the key fingerprint check):
The
--pinnames mise's signing repository, so the release must be signed by that identity. Packslip then picks the artifact for your platform, checks its digest and size, stages the complete tree, and exports only the commands the manifest declares. It prints the paths it created and tells you what to add to PATH; it doesn't edit PATH for you.Without a pin, the first install trusts the repository GitHub reports for that name and remembers its identity. Later installs must match: a deleted-and-recreated repository is refused, and a pin follows a repository transfer rather than its owner. Changing a remembered signer requires an explicit
--accept-trust-change=<id>for that exact proposal.What it does and doesn't promise
Verification proves who signed a release and that the artifact you downloaded matches what they signed. It doesn't prove the publisher's source, CI, or release workflow is uncompromised, and it doesn't make the installed program safe to run.
Stamps are packslip's early answer to that gap. A third party, such as a registry, scanner, or review service, can sign a list of the releases it has checked. Each listed release carries that host's stamp. A consumer that trusts the host then installs only stamped versions, and still verifies each one against the vendor's own signature. mise already supports this through its
packslip.stamperssetting, butpackslip installdoesn't yet, and no public stamping service exists so far. See Publish or trust a third-party list.packslip installalso stays deliberately small. It runs no post-install scripts, edits no shell startup files, installs no dependencies, switches no versions, and doesn't track upgrades in the background. The installed tool handles its own setup and self-updates, such asmise self-update.Where it's available
Release binaries cover Linux x64 and ARM64, macOS ARM64, and Windows x64 and ARM64. Intel Macs aren't a release target. You can get packslip from:
ppa:jdxcode/packslip(Resolute), for x64 and ARM64jdxcode/packslip(Fedora 44), for x64 and ARM64mise use -g packslipinstall.shandinstall.ps1ghcr.io/jdx/packslipThe APT, RPM, PPA, and COPR channels are run upstream, not by official distribution archives such as Debian main. Packagers who want to carry packslip can build it offline from the source recipes with their distribution's own Rust toolchain.
Reference: discovery, paths, and long-term support
Command.
packslip install <project> [--version V] [--pin P]...accepts a GitHub repository (with an optional monorepo subpath) or a project on its own domain. GitHub projects are discovered from release data and optional signed supplementary lists; domain projects use a signed well-known list. Invalid, expired, withdrawn, rolled-back, or missing-after-acceptance metadata fails the install.--offlineuses only authenticated cached metadata and artifacts, including a fresh cached TUF root.--trusted-rootlets an administrator supply the Sigstore root instead.Paths.
$XDG_DATA_HOME/packslip/, commands in~/.local/bin, state in$XDG_STATE_HOME/packslip/--system)/opt/packslip/, commands in/usr/local/bin, state in/var/lib/packslip/Unix commands are symlinks; Windows commands are native launchers. Extraction is staged, size-bounded, and validated, and replacement is journaled so an interrupted install recovers.
--forcereplaces conflicting command entries but never skips signature, identity, freshness, host, or archive checks.Long-term support. The manifest format is stable, but an old verifier still needs current trust metadata and a signing format it understands. Security fixes, new signing formats, or missing historical material can require a verifier update. The published compatibility matrix is evidence to help distribution maintainers decide what to carry, not a guarantee of how long any version stays usable.
Feedback wanted
We'd especially like to hear from:
AI-assisted. Original drafting: Claude Code (Claude Fable 5.1); revisions: Codex, Claude Code.
All reactions