Skip to content

fix(installers): verify resq build provenance and stop masking API errors - #100

Merged
WomB0ComB0 merged 2 commits into
mainfrom
fix/install-resq-hardening
Oct 3, 2026
Merged

WomB0ComB0 merged 2 commits into
mainfrom
fix/install-resq-hardening

Conversation

@WomB0ComB0

@WomB0ComB0 WomB0ComB0 commented Oct 3, 2026 •

Copy link
Copy Markdown
Member

Summary

The three resq installers (scripts/install-resq.sh, install.sh, install.ps1) verified a download only against SHA256SUMS. That file comes from the same release as the archive, so anyone able to publish the release can publish matching sums. resq-software/crates attaches a Sigstore build attestation to every binary release (0.5.0–0.5.2 checked), but none of the installers looked at it.

This PR also fixes a few long-standing defects in how install-resq.sh picks and installs a release.

Changes (all three installers unless noted)

  • Tag choice. The installers now pick the highest stable resq-cli-vX.Y.Z by number. The API lists releases by creation date and crates releases several crates at once, so "first match" picked the newest-created release, not the newest version. Pre-releases, drafts and other crates' tags are never picked.

  • API errors are errors (install-resq.sh). The old curl | sed | head ran under set -eu without pipefail, so a failed request (network, proxy, the 60/hour rate limit) came out as an empty tag. That read as "no release exists" and the script quietly fell back to building from source.

  • Provenance check. When gh 2.68+ is available (probed by --source-digest, not just the subcommand: 2.49–2.67 have attestation verify without it), the installers:

    • resolve the tag to its commit and require it to be on crates master;
    • run:
      gh attestation verify <archive> --repo resq-software/crates \
        --cert-identity https://github.com/resq-software/crates/.github/workflows/release.yml@refs/tags/<tag> \
        --cert-oidc-issuer https://token.actions.githubusercontent.com \
        --source-ref refs/tags/<tag> --source-digest <sha> --deny-self-hosted-runners
      
    • match the signer exactly (--cert-identity, never the prefix-matching --signer-workflow).

    A check that runs and fails installs nothing. Without a capable gh, the install continues on the checksum and says so. RESQ_REQUIRE_PROVENANCE=1 makes that a refusal.

  • Safer install. The extracted binary must report the tag's version before it replaces anything. It is staged beside the target and renamed into place, and the old one is kept as resq.prev. Under dash, a signal no longer leaves the staging file behind.

  • Install receipt at ${XDG_CONFIG_HOME:-~/.config}/resq/install.json (Windows: %APPDATA%\resq\install.json):

    {"schema": 1, "method": "release|cargo", "ref": "resq-cli-v0.5.2", "path": "...", "sha256": "...", "provenance": "verified|unchecked|n/a", "installed_at": "2026-10-03T22:10:00Z"}

    This is so a future resq self update replaces only a binary a verified release installed, never a cargo-, nix- or hand-built one.

  • Flag conflict (install-resq.sh). RESQ_ALLOW_UNVERIFIED=1 together with RESQ_REQUIRE_PROVENANCE=1 is refused as a contradiction.

  • Docs. README's install-resq.sh row said the cargo fallback was "the only path". It no longer is. The new variable and the receipt are documented, and install.sh --help lists RESQ_REQUIRE_PROVENANCE.

  • VERSION 0.4.7, re-stamped. Merging this is a release.

Verified against the real releases

  • Strict gh attestation verify passes for resq-cli-v0.5.2's Linux x64 asset, and fails for a wrong tag identity and for a tampered file.
  • The certificate of resq-cli-v0.5.2 names release.yml@refs/tags/resq-cli-v0.5.2, source digest 72e0ae4, on a GitHub-hosted runner.
  • resq-cli-v0.5.0, v0.5.1 and v0.5.2 are all attested, and their tag commits are on crates master.

Test plan

  • tests/installers/release-path.sh (new, hermetic: stubbed curl/gh/cargo, each case under env -i with a scratch HOME): 15/15. Against the old install-resq.sh, 12 of the original 13 cases fail; the one that passes is the existing checksum-mismatch guard.
  • install.ps1 harness (52 checks, run against the real function via its AST): 52/52 with a modern gh. With a gh lacking --source-digest, exactly the provenance-verified cases turn into "unchecked".
  • install.sh function smoke-tested under sh, dash, and bash -o pipefail with a stub gh. stdout stays empty.
  • sh tests/run_all.sh: all 6 suites pass. tests/installers/pinning.sh no longer writes a receipt into the real ~/.config.
  • shellcheck repo-wide, install.sh as POSIX sh, dash -n. Both .ps1 files parse under pwsh 7.6. actionlint and zizmor are clean on required.yml. Every required.yml shellcheck-job step runs locally and passes.
  • Adversarial review (3 lenses, each finding independently verified): 7 distinct findings confirmed, all fixed here.
  • CI required, windows-smoke (Windows PowerShell 5.1 runs only there)
  • After merge: the release run publishes SHA256SUMS + SBOM with an attestation. That depends on fix(release): attest and attach the SBOM before the immutable Release exists #99 merging first.

Not in this PR

  • The receipt's consumer (resq self update) lives in resq-software/crates. The location and schema above are the contract.
  • install.sh and install.ps1 keep their existing "warn and skip" convention for a failed check during onboarding, rather than aborting the whole setup.

Summary by CodeRabbit

  • New Features

    • Installers now select the highest stable release and verify release checksums. Where supported, they also verify build provenance; settings are available to require provenance or allow installation when verification is unavailable.
    • Installers validate the binary’s version before replacing an existing installation, preserve the previous binary when possible, and record installation details in a receipt.
    • Cargo fallback remains available for unsupported platforms or releases without a matching binary.
  • Bug Fixes

    • GitHub API errors and failed verification checks now stop installation rather than being treated as a missing release.
  • Documentation

    • Updated installation guidance and CI coverage descriptions.

…rors

All three resq installers (install-resq.sh, install.sh, install.ps1):
- pick the highest stable resq-cli-vX.Y.Z by number, not the first-created release;
- when gh 2.68+ can check it, verify the Sigstore attestation with the exact release.yml@refs/tags/<tag> signer, the tag's commit as source digest, hosted runners only, and require that commit to be on crates master. A check that runs and fails installs nothing. RESQ_REQUIRE_PROVENANCE=1 makes an unavailable check fatal;
- require the new binary to report the tag's version, stage it beside the target, keep the old one as resq.prev;
- write an install receipt (${XDG_CONFIG_HOME:-~/.config}/resq/install.json; %APPDATA%\resq on Windows) so a later self-update replaces only binaries a verified release installed.

install-resq.sh no longer turns a GitHub API error into an empty tag and a silent source build, cleans up its staging file on a signal under dash, and refuses RESQ_ALLOW_UNVERIFIED=1 combined with RESQ_REQUIRE_PROVENANCE=1. tests/installers/release-path.sh covers it hermetically (15 cases; 12 of the original 13 fail against the old script), and pinning.sh no longer writes a receipt into the real config dir. VERSION 0.4.7.
@WomB0ComB0
WomB0ComB0 requested a review from a team as a code owner October 3, 2026 22:38
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
get-resq-software efd1ca0 Oct 03 2026, 10:50 PM

@github-actions github-actions Bot added size/XL PR size: extra large (500-999 lines changed) C-Bug Something isn't working area:tests Changes under tests/ A-Release Release plumbing (VERSION, pins, stamp, release workflow) area:installers Changes to install.sh / install.ps1 A-DevOps CI, workflows, actions, and repo infra (.github) area:scripts Changes under scripts/ C-Documentation Improvements or additions to documentation and removed C-Bug Something isn't working size/XL PR size: extra large (500-999 lines changed) labels Oct 3, 2026
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 011c801c-42cc-4c79-a3a5-9f3e2a99cf14
📥 Commits

Reviewing files that changed from the base of the PR and between 6cd46d2 and efd1ca0.

📒 Files selected for processing (9)
  • .github/workflows/required.yml
  • README.md
  • VERSION
  • install.ps1
  • install.sh
  • scripts/install-resq.sh
  • tests/installers/pinning.sh
  • tests/installers/release-path.sh
  • tests/run_all.sh

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The three installers now select stable releases, check release artifacts and provenance, validate binaries before replacement, and write installation receipts. New installer tests cover release selection, verification outcomes, failed checks, upgrades, and receipt creation.

Changes

Release installer flow

Layer / File(s) Summary
Select stable releases
README.md, VERSION, install.ps1, install.sh, scripts/install-resq.sh, .github/workflows/required.yml
The installers select the highest stable release tag from release metadata and stop when the release API request fails. The README describes release selection and the conditions for Cargo fallback. Installer versions advance to 0.4.7.
Verify release artifacts and provenance
install.ps1, install.sh, scripts/install-resq.sh, README.md, tests/installers/release-path.sh
The installers check release checksums and can verify Sigstore provenance. Their settings control required provenance and, for install-resq.sh, handling when verification cannot run. The new tests cover checksums, provenance, and related failure cases.
Validate, replace, and record installations
install.ps1, install.sh, scripts/install-resq.sh, tests/installers/*, tests/run_all.sh, .github/workflows/required.yml, README.md
The installers check the binary version, stage it beside the destination, preserve the previous binary when possible, and write receipts. The test runner and required workflow include the release-path test. README coverage and installer test isolation are updated.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Installer as install-resq.sh
  participant GitHubAPI as GitHub releases API
  participant GH as gh CLI
  participant InstallDir as install directory
  Installer->>GitHubAPI: Request release metadata
  GitHubAPI-->>Installer: Return release tags and assets
  opt Provenance verification is available
    Installer->>GH: Verify release attestation
    GH-->>Installer: Return verification result
  end
  Installer->>InstallDir: Stage and replace validated binary
Loading

Suggested reviewers: resq-sw

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 5 files. (4 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main installer changes: provenance verification and treating GitHub API errors as errors.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 5 files. (4 skipped: 4 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

AI Auditor: LGTM!

I have reviewed the changes in this pull request and found no security vulnerabilities, logic bugs, or performance issues. The changes significantly improve the security and robustness of the installation scripts by adding build provenance verification, atomic installations, and better error handling. The addition of comprehensive tests is also a great improvement.

Well done!

Generated by ai-auditor for #100 · gem25pro · 204.8 AIC · ⌖ 3.97 AIC · ⊞ 12.8K · ◷

@github-actions github-actions Bot added size/XL PR size: extra large (500-999 lines changed) C-Bug Something isn't working and removed C-Documentation Improvements or additions to documentation labels Oct 3, 2026
@WomB0ComB0
WomB0ComB0 merged commit 10a7418 into main Oct 3, 2026
32 checks passed
@WomB0ComB0
WomB0ComB0 deleted the fix/install-resq-hardening branch October 3, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-DevOps CI, workflows, actions, and repo infra (.github) A-Release Release plumbing (VERSION, pins, stamp, release workflow) area:installers Changes to install.sh / install.ps1 area:scripts Changes under scripts/ area:tests Changes under tests/ C-Bug Something isn't working size/XL PR size: extra large (500-999 lines changed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants