Skip to content

Commit 18b02e4

Browse files
feat: add build verification and attestation to releases
- Generate SHA256 checksums for each release - Add GitHub Actions build attestation - Create VERIFICATION.md for each release - Include verification instructions in release notes - Document reproducible build process
1 parent 189281f commit 18b02e4

File tree

2 files changed

+250
-48
lines changed

2 files changed

+250
-48
lines changed

.github/workflows/release.yml

Lines changed: 133 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,148 @@ name: Release
33
on:
44
push:
55
tags:
6-
- 'v*'
6+
- 'v*'
77
workflow_dispatch:
88
inputs:
99
version:
1010
description: 'Release version (e.g., v1.0.0)'
1111
required: false
1212
type: string
1313

14+
permissions:
15+
contents: write
16+
attestations: write
17+
id-token: write
18+
1419
jobs:
1520
build-release:
1621
runs-on: macos-26
1722

1823
steps:
19-
- name: Checkout code
20-
uses: actions/checkout@v4
21-
22-
- name: Setup Swift
23-
uses: maartene/setup-swift@main
24-
with:
25-
swift-version: '6.2'
26-
27-
- name: Build Release
28-
run: swift build -c release
29-
30-
- name: Create Release
31-
if: github.event_name == 'push'
32-
uses: softprops/action-gh-release@v1
33-
with:
34-
name: ${{ github.ref_name }}
35-
draft: false
36-
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
37-
files: |
38-
.build/release/container-compose
39-
env:
40-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41-
42-
- name: Upload to Release
43-
if: github.event_name == 'workflow_dispatch'
44-
run: |
45-
# Create release if it doesn't exist
46-
VERSION="${{ github.inputs.version || 'latest' }}"
47-
TAG="v${VERSION#v}"
48-
49-
# Create or get release ID
50-
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases/tags/$TAG --jq '.id' 2>/dev/null || echo "")
51-
52-
if [ -z "$RELEASE_ID" ]; then
53-
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases -X POST \
54-
--field tag_name="$TAG" \
55-
--field name="$TAG" \
56-
--field draft=false \
57-
--jq '.id')
58-
fi
59-
60-
# Upload asset
61-
gh api repos/${{ github.repository }}/releases/$RELEASE_ID/assets \
62-
-F "file=@.build/release/container-compose" \
63-
-F "name=container-compose"
64-
env:
65-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Swift
28+
uses: maartene/setup-swift@main
29+
with:
30+
swift-version: '6.2'
31+
32+
- name: Build Release
33+
run: swift build -c release
34+
35+
- name: Generate SHA256 Checksum
36+
id: checksum
37+
run: |
38+
shasum -a 256 .build/release/container-compose | awk '{print $1}' > .build/release/container-compose.sha256
39+
echo "sha256=$(cat .build/release/container-compose.sha256)" >> $GITHUB_OUTPUT
40+
echo "Binary SHA256: $(cat .build/release/container-compose.sha256)"
41+
42+
- name: Attest Build Provenance
43+
uses: actions/attest-build-provenance@v1
44+
with:
45+
subject-path: '.build/release/container-compose'
46+
47+
- name: Generate Verification Instructions
48+
run: |
49+
cat > .build/release/VERIFICATION.md << 'EOF'
50+
# Binary Verification
51+
52+
## SHA256 Checksum
53+
```
54+
${{ steps.checksum.outputs.sha256 }} container-compose
55+
```
56+
57+
## Verify Download
58+
```bash
59+
# macOS/Linux
60+
shasum -a 256 ./container-compose | grep ${{ steps.checksum.outputs.sha256 }}
61+
62+
# Should output matching hash
63+
```
64+
65+
## Build Reproducibility
66+
This binary was built with:
67+
- macOS 26.x
68+
- Xcode 26.3
69+
- Swift 6.2
70+
- Apple Silicon (arm64)
71+
72+
## Attestation
73+
This release includes cryptographic attestation via GitHub Actions.
74+
Verify attestation: `gh attestation verify container-compose --owner explicitcontextualunderstanding`
75+
76+
## Source Code
77+
Tagged commit: ${{ github.sha }}
78+
Compare: https://github.com/${{ github.repository }}/compare/${{ github.ref_name }}
79+
EOF
80+
81+
- name: Create Release with Verification
82+
if: github.event_name == 'push'
83+
uses: softprops/action-gh-release@v1
84+
with:
85+
name: ${{ github.ref_name }}
86+
draft: false
87+
prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') }}
88+
files: |
89+
.build/release/container-compose
90+
.build/release/container-compose.sha256
91+
.build/release/VERIFICATION.md
92+
body: |
93+
## Verification
94+
95+
**SHA256:** `${{ steps.checksum.outputs.sha256 }}`
96+
97+
Verify your download:
98+
```bash
99+
shasum -a 256 ./container-compose | grep ${{ steps.checksum.outputs.sha256 }}
100+
```
101+
102+
**Build Attestation:** This release includes cryptographic attestation.
103+
```bash
104+
gh attestation verify container-compose --owner explicitcontextualunderstanding
105+
```
106+
107+
See VERIFICATION.md for full details.
108+
env:
109+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
110+
111+
- name: Upload to Release (workflow_dispatch)
112+
if: github.event_name == 'workflow_dispatch'
113+
run: |
114+
# Create release if it doesn't exist
115+
VERSION="${{ github.inputs.version || 'latest' }}"
116+
TAG="v${VERSION#v}"
117+
118+
# Create or get release ID
119+
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases/tags/$TAG --jq '.id' 2>/dev/null || echo "")
120+
121+
if [ -z "$RELEASE_ID" ]; then
122+
RELEASE_ID=$(gh api repos/${{ github.repository }}/releases -X POST \
123+
--field tag_name="$TAG" \
124+
--field name="$TAG" \
125+
--field draft=false \
126+
--field body="## Verification
127+
128+
**SHA256:** \`${{ steps.checksum.outputs.sha256 }}\`
129+
130+
Verify your download:
131+
\`\`\`bash
132+
shasum -a 256 ./container-compose | grep ${{ steps.checksum.outputs.sha256 }}
133+
\`\`\`
134+
135+
See full verification instructions in VERIFICATION.md" \
136+
--jq '.id')
137+
fi
138+
139+
# Upload assets
140+
gh api repos/${{ github.repository }}/releases/$RELEASE_ID/assets \
141+
-F "file=@.build/release/container-compose" \
142+
-F "name=container-compose"
143+
gh api repos/${{ github.repository }}/releases/$RELEASE_ID/assets \
144+
-F "file=@.build/release/container-compose.sha256" \
145+
-F "name=container-compose.sha256"
146+
gh api repos/${{ github.repository }}/releases/$RELEASE_ID/assets \
147+
-F "file=@.build/release/VERIFICATION.md" \
148+
-F "name=VERIFICATION.md"
149+
env:
150+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

VERIFICATION.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Binary Verification
2+
3+
This document describes how to verify that your downloaded `container-compose` binary matches the source code in this repository.
4+
5+
## Release Checksums
6+
7+
Each release includes a SHA256 checksum file. You can verify your download matches the official release.
8+
9+
### Latest Release (v0.10.1)
10+
11+
| File | SHA256 |
12+
|------|--------|
13+
| container-compose | *(See release notes for current hash)* |
14+
15+
## Verification Steps
16+
17+
### 1. Download and Verify
18+
19+
```bash
20+
# Download the binary
21+
curl -L -o container-compose \
22+
https://github.com/explicitcontextualunderstanding/Container-Compose/releases/latest/download/container-compose
23+
24+
# Download the checksum file
25+
curl -L -o container-compose.sha256 \
26+
https://github.com/explicitcontextualunderstanding/Container-Compose/releases/latest/download/container-compose.sha256
27+
28+
# Verify
29+
shasum -a 256 -c container-compose.sha256
30+
31+
# Or manually check
32+
shasum -a 256 ./container-compose
33+
```
34+
35+
### 2. GitHub Attestation
36+
37+
Each release is cryptographically attested via GitHub Actions. Verify the attestation:
38+
39+
```bash
40+
# Using GitHub CLI
41+
gh attestation verify container-compose --owner explicitcontextualunderstanding
42+
43+
# Or with specific release
44+
gh attestation verify container-compose \
45+
--repo explicitcontextualunderstanding/Container-Compose \
46+
--predicate-type https://slsa.dev/provenance/v1
47+
```
48+
49+
## Reproducible Builds
50+
51+
You can rebuild the binary from source and compare:
52+
53+
### Build Environment
54+
55+
- **OS**: macOS 26.x
56+
- **Xcode**: 26.3
57+
- **Swift**: 6.2
58+
- **Architecture**: arm64 (Apple Silicon)
59+
60+
### Build Steps
61+
62+
```bash
63+
# Clone the repository
64+
git clone https://github.com/explicitcontextualunderstanding/Container-Compose.git
65+
cd Container-Compose
66+
67+
# Checkout the release tag
68+
git checkout v0.10.1
69+
70+
# Build release binary
71+
./build-release.sh
72+
73+
# Generate checksum
74+
shasum -a 256 .build/release/container-compose
75+
```
76+
77+
### Expected Result
78+
79+
The SHA256 hash should match the release checksum file. Note that fully reproducible builds require the exact same toolchain versions.
80+
81+
## Security
82+
83+
### Trust Model
84+
85+
1. **Source Code**: Publicly auditable on GitHub
86+
2. **CI/CD**: GitHub Actions with transparent build logs
87+
3. **Attestation**: Cryptographic proof of build provenance
88+
4. **Checksums**: SHA256 for integrity verification
89+
90+
### Reporting Issues
91+
92+
If verification fails:
93+
1. Check your download completed successfully
94+
2. Verify you're using the correct platform binary
95+
3. Open an issue at: https://github.com/explicitcontextualunderstanding/Container-Compose/issues
96+
97+
## Verification Automation
98+
99+
Add to your CI/CD pipeline:
100+
101+
```yaml
102+
- name: Verify container-compose
103+
run: |
104+
curl -L -o container-compose.sha256 \
105+
https://github.com/explicitcontextualunderstanding/Container-Compose/releases/download/v0.10.1/container-compose.sha256
106+
EXPECTED_HASH=$(cut -d' ' -f1 container-compose.sha256)
107+
ACTUAL_HASH=$(shasum -a 256 /usr/local/bin/container-compose | cut -d' ' -f1)
108+
if [ "$EXPECTED_HASH" != "$ACTUAL_HASH" ]; then
109+
echo "Verification failed! Expected: $EXPECTED_HASH, Got: $ACTUAL_HASH"
110+
exit 1
111+
fi
112+
echo "✓ Binary verified"
113+
```
114+
115+
---
116+
117+
*Last updated: 2026-03-24*

0 commit comments

Comments
 (0)