Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/mac.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,8 @@ jobs:
name: Build & package Blurt (release)
permissions:
contents: write # needed to publish a GitHub Release on tag pushes
id-token: write # OIDC token the attestation is signed against
attestations: write # write the SLSA provenance statement
steps:
- name: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand DownExpand Up@@ -150,6 +152,31 @@ jobs:
echo "zip=$ZIP" >> "$GITHUB_OUTPUT"
echo "name=$(basename "${ZIP%.zip}")-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"

# Binds this artifact's digest to the repo, workflow and commit that
# produced it. Notarization proves Apple saw the binary and that it
# carries our Developer ID; it says nothing about which source tree it
# was built from, and a checksum only proves the file didn't change in
# transit. Consumers verify with:
# gh attestation verify Blurt-macOS.zip --repo lightware-dev/blurt
#
# Only on release tags: attestation is about what we publish, and a PR
# build isn't published. The version-less Blurt-macOS.zip copy needs no
# separate attestation — it's byte-identical, and attestations are keyed
# by digest rather than by filename.
#
# Caveat worth knowing: this attests the bytes *this run* built. On a
# re-run where the release already carries an earlier attempt's asset,
# the upload step deliberately leaves the published file untouched (see
# below) and a macOS rebuild is never byte-identical — fresh codesign
# timestamp, new notarization ticket. The attestation would then cover a
# digest that isn't the published one. Same failure mode the cask bump
# already guards against; if it bites, delete the asset and re-run clean.
- name: attest build provenance
if: steps.cfg.outputs.release == 'true'
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: ${{ steps.artifact.outputs.zip }}

- name: clean up signing keychain
if: always() && steps.cfg.outputs.signed == 'true'
run: security delete-keychain "$RUNNER_TEMP/signing.keychain-db" || true
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/windows.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,6 +30,8 @@ jobs:
name: Build & package Blurt (release)
permissions:
contents: write # needed to publish a GitHub Release on tag pushes
id-token: write # OIDC token the attestation is signed against
attestations: write # write the SLSA provenance statement
defaults:
run:
shell: pwsh
Expand DownExpand Up@@ -120,6 +122,26 @@ jobs:
$sha = "$env:GITHUB_SHA".Substring(0,7)
"name=Blurt-$ver-Windows-$sha" >> $env:GITHUB_OUTPUT

# Binds this artifact's digest to the repo, workflow and commit that
# produced it:
# gh attestation verify Blurt-Windows.zip --repo lightware-dev/blurt
#
# Carries more weight here than on macOS, because Windows releases are
# unsigned (#9) — this is currently the only cryptographic link between
# a downloaded zip and this repository. It is not a substitute for
# signing: it defends a release asset replaced *without* a code push,
# not an attacker with repo write access, who can push a tag and have CI
# attest their payload just as readily.
#
# Release tags only; a PR build isn't published. The version-less
# Blurt-Windows.zip copy is byte-identical and attestations are keyed by
# digest, so one statement covers both.
- name: attest build provenance
if: steps.cfg.outputs.release == 'true'
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: ${{ steps.artifact.outputs.zip }}

- name: upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -218,6 +218,15 @@ https://github.com/lightware-dev/blurt/releases/latest/download/Blurt-macOS.zip
Unzip, drag **Blurt.app** to `/Applications`, and open it — it's a universal
(Apple Silicon + Intel) menu-bar app that launches without Gatekeeper warnings.

Releases carry a signed [build provenance
attestation](https://docs.github.com/actions/security-guides/using-artifact-attestations)
binding the download to the workflow run and commit that built it — notarization
proves Apple saw the binary, not which source tree it came from. To check:

```bash
gh attestation verify Blurt-macOS.zip --repo lightware-dev/blurt
```

### Build from source

Built on the Mac (needs Xcode command-line tools):
Expand All@@ -239,14 +248,24 @@ Settings). See `clients/mac/README.md` for details
## Windows client

A native .NET 8 / WPF tray app — the Windows twin of the Mac client, same server
protocol and behaviour. Grab the latest signed build from the
protocol and behaviour. Grab the latest build from the
[**Releases page**](https://github.com/lightware-dev/blurt/releases/latest), or the
stable URL:

```
https://github.com/lightware-dev/blurt/releases/latest/download/Blurt-Windows.zip
```

Windows builds are **not** Authenticode-signed, so SmartScreen warns on first
launch — choose *More info* → *Run anyway*. They do carry a signed [build
provenance attestation](https://docs.github.com/actions/security-guides/using-artifact-attestations),
which is currently the only cryptographic link between the zip and this
repository:

```bash
gh attestation verify Blurt-Windows.zip --repo lightware-dev/blurt
```

Build from source (needs the .NET 8 SDK):

```bash
Expand Down