Skip to content

Sign release artifacts, and close the unpinned cargo fallback in install-resq.sh #58

Description

@WomB0ComB0

Raised in review of #57. Two problems, ordered by how live they are.

1. The cargo fallback is unpinned — and it is the only path that runs today

scripts/install-resq.sh prefers a digest-verified GitHub Release asset and falls back to cargo install. But resq-software/crates publishes no resq-cli-v* releases at all, so resolve_tag() returns empty and every invocation lands here:

# install-resq.sh:68 — WANTED_VERSION unset, so no --tag
cargo install --git "https://github.com/resq-software/crates" resq-cli

No --tag, no --rev. So this:

curl -fsSL https://get.resq.software/resq.sh | sh

builds and installs from crates default-branch HEAD at that instant. Unpinned, mutable, unverified — and unlike the asset path, this branch is not gated by RESQ_ALLOW_UNVERIFIED; it is the silent default.

The Worker hands over resq.sh with full chain of custody — pinned commit SHA, SHA-256 verified, fail-closed — and resq.sh then discards that guarantee one hop later. The verified part is the wrapper; the unverified part is the payload.

Worth stating plainly: the digest-verification machinery in that script is not wrong, it is simply unreachable. The bug is that its fallback is weaker than its primary path and nothing says so.

Fix

  • Publish resq-cli-v* releases from crates with per-target assets + SHA256SUMS, which makes the existing verified path live
  • Pin the fallback: cargo install --git <repo> --rev <40-hex> resq-cli, never a bare branch build
  • Gate the fallback behind RESQ_ALLOW_UNVERIFIED=1 when it cannot be pinned, matching how every other unverifiable branch in this repo already behaves
  • Test asserting the unpinned form is unreachable without the opt-out

2. No signatures — verification requires trusting GitHub

Even on the asset path, install-resq.sh fetches the artifact and its SHA256SUMS from the same GitHub release. That detects corruption in transit; it does not detect a compromised or coerced release. Both sides come from one origin, so the digest proves internal consistency, not provenance. (The script's own awk comment already mentions .sig files it never checks.)

Same limitation on get.resq.software: pinned digests ship with the deploy rather than the response, which defeats a tampering CDN — but there is still no key a third party can verify against offline.

Recommendation: Sigstore/cosign

The org already ships @resq-systems/types to npm with provenance: true, which is Sigstore-backed — so this is an extension of tooling already in use, not a new dependency.

  • cosign sign-blob each release asset in CI; publish .sig + .pem alongside
  • Verify by identity (--certificate-identity = the release workflow, --certificate-oidc-issuer = GitHub Actions), so there is no long-lived private key to hold
  • Add attestations: write and actions/attest-build-provenance for SLSA provenance on the binaries
  • Teach install-resq.sh to verify when cosign is present, and to say clearly what was and was not verified when it is absent — fail closed unless RESQ_ALLOW_UNVERIFIED=1, matching the existing convention
  • Document offline verification in README, including the identity to check against

minisign is the smaller alternative — one key, one 100-line dependency, trivially verifiable offline — but it needs private-key custody and rotation, which keyless Sigstore avoids. Recommend cosign; minisign only if a reviewer specifically wants an air-gapped path with no OIDC.

Why this is worth doing before it is asked for

The current answer to "how do I verify what you shipped" is "trust GitHub, and trust our CDN." For public-safety and DoD SBIR diligence the expected answer is "here is the signature and the public identity, verify it offline without trusting us." That is a design choice now and a finding later.

Concretely, the property to reach: a reviewer with no network access to us, holding only the artifact and our public identity, can prove the binary came from our release pipeline.

Scope note

Spans two repos: crates publishes and signs; dev verifies in install-resq.sh. Item 1 is the live exposure and should not wait for item 2 — pinning the fallback is a few lines and closes an actual hole today.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions