Skip to content

Serve the CLI repository's current installers and pin them - #458

Merged
rmcdaniel merged 2 commits into
mainfrom
fix/sync-cli-installers
Nov 4, 2026
Merged

rmcdaniel merged 2 commits into
mainfrom
fix/sync-cli-installers

Conversation

@rmcdaniel

Copy link
Copy Markdown
Member

Security fix. https://durable-workflow.com/install.sh and /install.ps1 back the docs' one-line CLI installs. They were stale copies of the CLI installers, last synced 2026-08-31 (e43e5a7f). Their opt-in attestation check (DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1) ran gh attestation verify --repo with no signer pin and no --deny-self-hosted-runners. The VERSION handling was also older.

Change

  • Installers: static/install.sh and static/install.ps1 are now byte-for-byte durable-workflow/cli 3df282fa scripts/install.sh (sha256 5f17db13…) and scripts/install.ps1 (sha256 52d3ef11…). cli named this source commit (lobby 8025). File modes are unchanged (100644).
    • The opt-in attestation check now requires the signer ^https://github.com/durable-workflow/cli/.github/workflows/(release|release-rust).yml@refs/(heads/main|tags/<semver>)$ and --deny-self-hosted-runners, in both installers.
  • scripts/installer-source.json: records the source repo, the commit and each file's sha256.
  • scripts/check-installer-source.test.js: wired into npm run build, so a drifted copy fails the build. The failure message says to re-copy and re-record.
  • .github/workflows/installers.yml: runs when either installer or its pin changes, or on workflow_dispatch. Permissions: contents: read, attestations: read.
    • install.sh on ubuntu-latest and macos-latest, and install.ps1 on windows-latest, each install VERSION=2.3.5 with DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1 and assert dw --version contains 2.3.5.
    • Negative case: a copy of the same installer that only trusts a non-existent not-release workflow must fail and leave no binary behind.

Evidence so far (lab2)

  • Linux (debian:bookworm-slim, bounded container, no credentials mounted): the default checksum path installs dw 2.3.5 (commit 5c03e4f31adb).
  • macOS arm64 host, DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1: "Verifying checksum" and "Verifying GitHub artifact attestations" both pass with the signer pin, and it installs dw 2.3.5.
  • Negative, same host: the not-release copy fails with Error: verifying with issuer "sigstore.dev", exit 1, and installs nothing.
  • Hosted: the new workflow adds the Windows install.ps1 end-to-end run, plus Linux and macOS with attestations. cli's own CI only syntax-checks install.ps1.

Follow-up: #457 (CLI 2.5.0) will copy cli#85's macOS-x86_64 change byte for byte when it merges, and update installer-source.json to that commit.

🤖 Generated with Claude Code

durable-workflow.com/install.sh and /install.ps1 were copies of the CLI
installers last synced in August. They verified attestations without the
CLI's signer pin (release or release-rust workflow on main or a release tag)
and without --deny-self-hosted-runners. Replace both byte for byte with
durable-workflow/cli 3df282fa scripts/install.sh and scripts/install.ps1,
record that source with sha256 values, and fail the build when either file
drifts. A new workflow installs the stable CLI end to end with
signer-pinned attestations on Linux, macOS and Windows, and checks that an
installer trusting another workflow refuses the release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Put the install directory on PATH so the installer's not-ready exit does
not fail a good install, require the negative case to fail at the
attestation step, reset PowerShell's exit code after the expected gh
refusal, and grant attestations: read at job level (Sonar S8264). The
installers and their recorded source are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 4, 2026

Copy link
Copy Markdown

@rmcdaniel

Copy link
Copy Markdown
Member Author

Hosted evidence at 565afc0a: 14/14 checks pass, Sonar quality gate OK. Installer workflow run 41774824735.

  • install.sh, ubuntu-latest and macos-latest, DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1: the checksum and the signer-pinned attestations verify, and dw --version prints dw 2.3.5 (commit 5c03e4f31adb). Installed and active versions agree.
  • install.ps1, windows-latest, same settings: the attestations verify and dw.exe --version prints dw 2.3.5 (commit 5c03e4f31adb).
  • Negative case, all three OSes: a copy that trusts only a not-release workflow is refused at "Verifying GitHub artifact attestations" with Error: verifying with issuer "sigstore.dev". Windows reports "Attestation verification failed". No binary is left behind.

The delta from 4cefc854 is workflow-only:

  • put the install directory on PATH (the installer exits 1 when dw isn't resolvable);
  • require the refusal to come from the attestation step;
  • reset LASTEXITCODE after the expected gh failure;
  • grant attestations: read at job level (Sonar S8264).

static/install.sh, static/install.ps1 and scripts/installer-source.json are unchanged.

@rmcdaniel

Copy link
Copy Markdown
Member Author

General interim review (Security unavailable)

Verdict: CLEAR at 565afc0 (independent reviewer)

  • Byte-equality: static/install.sh (sha256 5f17db13…3b18) and static/install.ps1 (52d3ef11…fd0b) match durable-workflow/cli scripts/install.{sh,ps1} at 3df282fa33ebe5e3319e68298aeef2c32d64c1af, which is also current cli main. scripts/installer-source.json records the correct commit, paths and hashes.
  • Drift test: scripts/check-installer-source.test.js passes clean and fails when either installer changes by one byte (mutation-tested). It runs inside npm run build, in the required "Executable documentation contracts" check (qualification.yml, on PRs and main) and in deploy.yml, so a drifted file can't deploy.
  • installers.yml:
    • Permissions: workflow-level contents: read, job-level contents: read + attestations: read.
    • No pull_request_target and no secrets beyond github.token. Checkout is pinned to the repo-standard SHA with persist-credentials false.
    • DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1 is set on every step.
    • The positive case checks that dw --version reports 2.3.5 on ubuntu, macos and windows.
    • The negative case swaps the signer pin, requires a non-zero exit at the attestation stage, and requires that no dw/dw.exe was installed. The $global:LASTEXITCODE = 0 reset is the last line of the negative step only, after every assertion.
    • Triggers: pull_request on the installer files plus workflow_dispatch.
  • Delta: 4cefc85..565afc0 touches only .github/workflows/installers.yml. The files are served unchanged at /install.sh and /install.ps1.
  • CI at 565afc0: 14/14 green, including SonarCloud and installer run 41774824735 on all three OSes.

Non-blocking follow-ups:

  • ! cmp -s (installers.yml:62) is errexit-exempt, though the next check still catches a no-op substitution.
  • "verifying with issuer" is gh's generic failure text.
  • The version check is a substring match.
  • The installer checks are neither required nor scheduled.
  • Sonar S131 at static/install.sh:64 must be fixed in the cli repo, to keep the copy byte-identical.

@rmcdaniel
rmcdaniel merged commit 96690cc into main Nov 4, 2026
14 checks passed
@rmcdaniel

Copy link
Copy Markdown
Member Author

Merged at cleared head 565afc0. The merge is 96690cc, with parents 858918a and 565afc0. The merge tree (0d314e6a) equals the head tree. A live check of /install.sh and /install.ps1 sha256 against installer-source.json after deploy is due from durable-workflow-github-io.

@rmcdaniel

Copy link
Copy Markdown
Member Author

Live readback after Pages deploy run 41775539825 at 96690cc8:

  • https://durable-workflow.com/install.sh: sha256 5f17db134cd04cc455de1908df0d0d80d2e8e7de0d89731b58689fbb70313b18
  • https://durable-workflow.com/install.ps1: sha256 52d3ef117eaa360527924a54401cb01d1d4aedaa5ca19c28846148eb39dffd0b

Both equal scripts/installer-source.json (durable-workflow/cli 3df282fa). The review follow-ups are in #460.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant