This document describes how dw is distributed, how operators verify the
artifacts they install, and which optional distribution-hardening surfaces
are deliberately out of scope for the 0.1.x line.
It is the canonical reference for security-conscious users who need to answer "is this binary the one the project built?" and "what does this CLI do behind my back?". The short answers are: yes, every published asset carries a checksum and a GitHub artifact attestation; and nothing — the CLI talks to the configured server and nothing else.
| Path | Audience | Verification baseline |
|---|---|---|
One-line installer (install.sh / install.ps1) |
Most users | SHA256 against the release SHA256SUMS manifest, optional GitHub artifact attestation when gh is installed and DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1 is set. |
| Direct download from the GitHub release | Operators bringing their own automation | verify-release.sh (bundled with each tagged release) plus gh attestation verify for end-to-end provenance. |
Homebrew formula (dw.rb) |
macOS arm64 users on Homebrew | The release-bundled formula pins the exact release URL and SHA256; brew install verifies the checksum before linking. |
composer global require durable-workflow/cli |
PHP-shop users | Composer pins by version in your composer.lock; verify the release tag before bumping. |
PHAR (dw.phar) plus a system PHP |
PHP-aware operators | Same SHA256SUMS plus attestation surface as the native binaries. |
The one-line installer is the recommended path on every supported platform.
Both install.sh and install.ps1 download the matching SHA256SUMS manifest,
verify the binary's checksum before writing it into the install directory,
and refuse to proceed when the checksum does not match.
The provenance boundary for the 0.1.x line is:
- The git tag — every release is built from a tag pushed to
durable-workflow/cli. The release workflow runs only onrefs/tags/<MAJOR>.<MINOR>.<PATCH>*. SHA256SUMS— generated inside the release workflow over every asset that is actually published for the tag (PHAR, every native binary that built successfully, the installer scripts, theverify-release.shhelper, and the generated Homebrew formula).- GitHub artifact attestations — the release workflow signs every
asset (including
SHA256SUMS) with the GitHub Actions OIDC identity viaactions/attest-build-provenance. Anyone can replay that signature withgh attestation verify <asset> --repo durable-workflow/cli.
Together these three layers answer the question "was this artifact produced by the durable-workflow/cli release workflow from the named tag?" without trusting any one infrastructure component on its own.
# Download every asset (or just the ones you care about) into a directory.
gh release download <tag> --repo durable-workflow/cli --dir release/
# Verify checksums for every local file listed in SHA256SUMS.
sh release/verify-release.sh release/
# Verify GitHub artifact attestations for the same files.
sh release/verify-release.sh --attest release/verify-release.sh is itself a release asset, so the verification helper
is pinned to the same tag as the binaries it checks.
Decision: explicitly out of scope for the 0.1.x line. GitHub artifact attestations are the primary provenance mechanism.
dw is distributed as a PHAR plus an embedded-PHAR static binary built
with static-php-cli's phpmicro SAPI. The native binaries are not the
output of a platform-native toolchain (Xcode, MSBuild) and the maintainers
do not currently hold the signing identities required to produce binaries
that pass Apple Notary Service review or carry an Authenticode signature
trusted by SmartScreen. Acquiring those identities, holding the signing
keys correctly, and integrating them into a hosted release workflow is a
distinct piece of operational work that we have intentionally not taken
on for the 0.1.x line — it would not strengthen the cryptographic
provenance of the binary, only its handling by the OS-level publisher
trust UX.
The actions/attest-build-provenance artifact attestation is a stronger
provenance signal than a typical OS-level code signature: it ties every
artifact to a specific commit, workflow run, and signed builder identity
issued by Sigstore's public transparency log via the GitHub Actions OIDC
provider. Operators who need machine-verifiable provenance can replay
that signature with gh attestation verify without trusting any
maintainer-held key material.
- macOS users may see a Gatekeeper prompt on first launch. The standard
xattr -d com.apple.quarantine /usr/local/bin/dwclears it. The one-line installer downloads viacurl, which does not stamp the quarantine attribute, so installer users typically do not hit this. - Windows users may see a SmartScreen prompt on first launch. The
dw.exebinary is unsigned. Choose "More info" → "Run anyway" if you have already verified the SHA256 and (optionally) the artifact attestation. - Operators who require a signed-and-notarized macOS or Windows binary
today should stage their own re-signing pipeline against the published
release artifacts using their organization's own developer ID. Any
re-signing pipeline must verify the upstream
SHA256SUMSand attestation before signing.
This decision is reviewable when one of the following becomes true:
- A Homebrew tap is published as the canonical macOS install path, in which case Apple notarization stops being load-bearing for new users.
- A maintainer-held Apple Developer ID and Authenticode signing identity are donated to the project alongside the operational commitment to protect those keys.
- A platform-native rebuild is added (for example, a Go or Rust port of the CLI front-end) that makes signing the build output trivial.
Decision: permanently out of scope. The CLI does not collect telemetry and will not gain a telemetry surface in the 0.1.x line.
dw makes no network requests except those that are a direct consequence
of an explicit command:
- HTTP requests to the
--serverURL (DURABLE_WORKFLOW_SERVER_URL, the resolved profile, or built-in defaults) for user-issued commands. - HTTPS requests to GitHub's release API on
dw upgrade, only when the user runs that command. - HTTP/HTTPS requests issued by
dw bridge:webhookand the external executor surfaces, only when the user invokes those commands and only to the targets the user supplies.
There is no:
- Background usage beacon.
- "Phone home" version check on startup or shutdown.
- Crash-report uploader, error reporter, or any analytics SDK.
- Update-availability poll outside of
dw upgrade.
A control-plane CLI runs against environments where any unsolicited
outbound traffic is a compliance failure. Operators who use dw against
production Durable Workflow servers must be able to assert "this CLI
talks to my control plane and nothing else" without auditing a feature
flag or trusting an opt-out toggle. The simplest mechanism for that
assertion is to never ship a telemetry surface at all.
This decision is fixed for the 0.1.x line. Any future telemetry surface would be opt-in by default, would be documented here before it shipped, and would be subject to the same provenance and verification expectations as the rest of the binary.
dw upgrade performs an explicit, user-invoked self-update. It downloads
the latest release asset for the current platform, verifies its SHA256
against the release manifest, and atomically replaces the running
binary. Behavior:
- Defaults:
dw upgradeupgrades to the newest release tag, refuses to proceed if checksum verification fails, and refuses to overwrite Composer-managed PHAR installs and Homebrew-managed binaries because the package manager owns those install paths. - Flags:
--version=<tag>for a specific release,--dry-runto print the action without performing it,--forceto override stale-binary guards, and--output=jsonfor scripted use.
There is no background update poll. The CLI never upgrades itself
without an explicit dw upgrade invocation.
Decision: in scope. The build is bit-reproducible from the same source tree, and a verifier ships in the repo so anyone can confirm it.
Given the same git tag, the same toolchain versions pinned in
.github/workflows/release.yml, and the sameSOURCE_DATE_EPOCHrecorded in the release notes, the publisheddw.pharis bit-identical to a locally rebuiltdw.pharfrom the same source.
The native binaries (dw-linux-*, dw-macos-aarch64,
dw-windows-x86_64.exe) embed the PHAR into a static-php-cli-built
phpmicro SAPI. The PHAR layer is reproducible by the contract above;
the SAPI layer reproduces given the same static-php-cli snapshot and
host toolchain, but pinning every host compiler to a deterministic point
is platform-specific work and is not required for the PHAR-level proof.
scripts/generate-build-info.phpderivesBUILD_DATEfromSOURCE_DATE_EPOCHwhen set, and fromDW_CLI_BUILD_DATEfirst when the caller wants an explicit override.scripts/build.shdefaultsSOURCE_DATE_EPOCHto the timestamp of theHEADcommit when the caller does not export one. It also normalizes the mtime on every input that ends up inside the PHAR so the archive entry headers do not vary across builds.- Box reads
SOURCE_DATE_EPOCHdirectly when stamping the gzip layer, so both the entry table and the compression frame are deterministic.
git checkout <tag>
make phar # one-shot baseline build
scripts/verify-reproducible-build.sh # builds twice, asserts SHA256 matchscripts/verify-reproducible-build.sh runs scripts/build.sh phar
twice from a clean state and fails if the resulting dw.phar is not
byte-identical between runs. CI runs the same check on every push so any
toolchain regression that breaks reproducibility is caught before it
reaches a tag.
# 1. Fetch the published artifact and its checksum manifest.
gh release download <tag> --repo durable-workflow/cli --dir release/ --pattern 'dw.phar' --pattern 'SHA256SUMS'
# 2. Build locally from the matching tag with the recorded epoch.
git checkout <tag>
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) make phar
# 3. Compare bytes.
sha256sum release/dw.phar build/dw.phar
diff <(sort release/SHA256SUMS) <(cd build && sha256sum dw.phar | sort)If the local sha256 does not match the published one, that is a reproducibility regression — please open a release-blocking issue.
The release workflow generates dw.rb, a Homebrew formula that pins the
exact release URL and SHA256 of dw-macos-aarch64, and publishes it as
a release asset alongside the binary. Operators who consume the CLI via
Homebrew today have two options until a public tap is registered:
-
Per-release install from the bundled formula.
gh release download <tag> --repo durable-workflow/cli --pattern dw.rb --output dw.rb brew install --formula ./dw.rb
The formula has the release URL and SHA256 baked in, so
brew installvalidates the binary's checksum against the locked-in value before linking it into the keg. -
Self-hosted tap. Vendor the generated
dw.rbinto your own tap repository (brew tap-new <org>/durable-workflow,cp dw.rb Formula/dw.rb, push), and your users can then runbrew install <org>/durable-workflow/dw. The formula's pinned URL and SHA256 keep the install path verifiable inside your tap as well.
A canonical durable-workflow/tap is on the roadmap. Until it lands,
the per-release install above is the supported path and should be
documented in any internal runbook that automates dw installs on macOS.
Before promoting dw into a production runbook:
- Pin to a specific release tag rather than
latest. - Configure your installer call with
DURABLE_WORKFLOW_INSTALL_VERIFY_ATTESTATIONS=1and ensureghis available so the installer enforces attestation as well as checksum. - For air-gapped operators: download the release directory ahead of
time, run
verify-release.sh --attestwhile you still have network access to GitHub's attestation API, then mirror the verified directory into your air-gapped environment. - Track the published
SOURCE_DATE_EPOCHper release if you intend to rebuild from source for compliance attestation.
- 0.1.x — Initial distribution policy. Signing/notarization out of scope,
telemetry permanently out of scope, reproducible-build contract
established with
verify-reproducible-build.sh.