diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d5332e..bb94a13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -224,6 +224,10 @@ jobs: runs-on: "ubuntu-22.04" outputs: val: ${{ steps.host.outputs.manifest }} + permissions: + "attestations": "write" + "contents": "write" + "id-token": "write" steps: - uses: actions/checkout@v4 with: @@ -266,6 +270,11 @@ jobs: run: | # Remove the granular manifests rm -f artifacts/*-dist-manifest.json + - name: Attest + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + artifacts/* - name: Create GitHub Release env: PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" diff --git a/README.md b/README.md index f95ef97..f29a7cc 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,29 @@ A command-line tool to find broken links in your static site. args: public/ --sources src/ ``` +The action downloads the prebuilt binary and verifies it against its [GitHub +build +attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) +before running (see #198). Verification uses the `gh` CLI: on GitHub-hosted +runners `gh` is present, so the check runs automatically. On runners without +`gh` — e.g. [Forgejo](https://forgejo.org/) or other non-GitHub CI — +verification is skipped with a warning so the action still works. + +Two environment variables override this: + +* `HYPERLINK_SKIP_ATTESTATION=1` skips verification entirely, even when `gh` is + available. +* `HYPERLINK_FORCE_ATTESTATION=1` requires verification and fails when `gh` is + missing. + +```yaml +- uses: untitaker/hyperlink@0.2.1 + env: + HYPERLINK_SKIP_ATTESTATION: "1" + with: + args: public/ --sources src/ +``` + ### NPM ```bash diff --git a/dist-workspace.toml b/dist-workspace.toml index c09736d..85b11d7 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -19,3 +19,7 @@ npm-scope = "@untitaker" install-path = "CARGO_HOME" # Whether to install an updater program install-updater = false +# Attest release artifacts (see #198); "host" phase so the installer — a global +# artifact — is covered, not just the per-target binaries. +github-attestations = true +github-attestations-phase = "host" diff --git a/scripts/install.sh b/scripts/install.sh index 8321447..3f7aeac 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -4,4 +4,30 @@ tag="`grep 'version = ' Cargo.toml | head -1 | cut -d'"' -f2`" echo "downloading hyperlink $tag" -curl --proto '=https' --tlsv1.2 -LsSf https://github.com/untitaker/hyperlink/releases/download/$tag/hyperlink-installer.sh | sh +# Download the installer to a file and verify it against its GitHub build +# attestation before running it (see #198). Verification needs the `gh` CLI; when +# `gh` is absent (e.g. Forgejo or other non-GitHub CI) the check is skipped with +# a warning. Set HYPERLINK_SKIP_ATTESTATION=1 to skip it entirely, or +# HYPERLINK_FORCE_ATTESTATION=1 to fail instead of skipping when `gh` is missing. +installer="`mktemp`" +trap 'rm -f "$installer"' EXIT + +curl --proto '=https' --tlsv1.2 -LsSf \ + "https://github.com/untitaker/hyperlink/releases/download/$tag/hyperlink-installer.sh" \ + -o "$installer" + +if [ -n "$HYPERLINK_SKIP_ATTESTATION" ]; then + echo "hyperlink: HYPERLINK_SKIP_ATTESTATION set, skipping attestation verification" >&2 +elif command -v gh >/dev/null 2>&1; then + # --signer-workflow pins the producing workflow, not just the repo. + gh attestation verify "$installer" \ + --repo untitaker/hyperlink \ + --signer-workflow untitaker/hyperlink/.github/workflows/release.yml +elif [ -n "$HYPERLINK_FORCE_ATTESTATION" ]; then + echo "hyperlink: HYPERLINK_FORCE_ATTESTATION is set but 'gh' is not installed; cannot verify attestation" >&2 + exit 1 +else + echo "hyperlink: 'gh' not found, skipping attestation verification (set HYPERLINK_FORCE_ATTESTATION=1 to require it)" >&2 +fi + +sh "$installer"