Skip to content

Commit 88802c7

Browse files
authored
feat(ci): sigstore build provenance attestations on cli/mcp/pysdk publishes (#26)
1 parent 66a287b commit 88802c7

4 files changed

Lines changed: 98 additions & 2 deletions

File tree

.github/workflows/publish-cli.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
permissions:
1010
contents: read
1111
id-token: write
12+
attestations: write
1213

1314
jobs:
1415
publish:
@@ -38,6 +39,15 @@ jobs:
3839
- name: Pack (sanity check)
3940
run: npm pack --dry-run
4041

42+
- name: Pack artifact
43+
run: npm pack
44+
45+
# pin: v3.2.0 -- actions/attest-build-provenance
46+
- name: Attest build provenance
47+
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f
48+
with:
49+
subject-path: 'cli/*.tgz'
50+
4151
- name: Publish to npm
4252
env:
4353
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/publish-mcp.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ on:
99
permissions:
1010
contents: read
1111
id-token: write
12+
attestations: write
1213

1314
jobs:
1415
publish:
@@ -32,6 +33,13 @@ jobs:
3233
run: npm test
3334
- name: Pack sanity
3435
run: npm pack --dry-run
36+
- name: Pack artifact
37+
run: npm pack
38+
# pin: v3.2.0 -- actions/attest-build-provenance
39+
- name: Attest build provenance
40+
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f
41+
with:
42+
subject-path: 'mcp/*.tgz'
3543
- name: Publish
3644
env:
3745
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.github/workflows/publish-pysdk.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ on:
2828
default: ''
2929

3030
permissions:
31-
contents: write # upload assets onto the GitHub release
32-
id-token: write # required for PyPI Trusted Publishing (OIDC)
31+
contents: write # upload assets onto the GitHub release
32+
id-token: write # required for PyPI Trusted Publishing (OIDC) + Sigstore attestations
33+
attestations: write # required for actions/attest-build-provenance
3334

3435
jobs:
3536
build-and-publish:
@@ -74,6 +75,12 @@ jobs:
7475
- name: Twine check
7576
run: python -m twine check dist/*
7677

78+
# pin: v3.2.0 -- actions/attest-build-provenance
79+
- name: Attest build provenance
80+
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f
81+
with:
82+
subject-path: 'python-sdk/dist/*'
83+
7784
# pin: v4.6.2 -- actions/upload-artifact
7885
- name: Upload artifacts to workflow
7986
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02

docs/ops/release-process.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,80 @@ Dependabot handles that on its weekly schedule.
202202
one-time setup. The token fallback remains in place until you remove it
203203
so the transition is risk-free.
204204

205+
## Build attestations (Sigstore)
206+
207+
Every `publish-*.yml` workflow generates a **Sigstore build provenance
208+
attestation** for the artifact it ships, using
209+
[`actions/attest-build-provenance@v3`](https://github.com/actions/attest-build-provenance)
210+
(SHA-pinned). The attestation is a signed statement, recorded in the
211+
GitHub attestations API and Sigstore's public transparency log, that
212+
says: "this exact byte-for-byte artifact was built by this exact
213+
workflow run on this commit". It's how the OpenSSF Scorecard
214+
`Signed-Releases` check verifies our releases.
215+
216+
| Workflow | Subject attested | When |
217+
|---|---|---|
218+
| `publish-cli.yml` | `cli/*.tgz` (output of `npm pack`) | after pack, before `npm publish` |
219+
| `publish-mcp.yml` | `mcp/*.tgz` (output of `npm pack`) | after pack, before `npm publish` |
220+
| `publish-pysdk.yml` | `python-sdk/dist/*` (sdist + wheel) | after `python -m build`, before PyPI upload |
221+
222+
For the npm packages the attestation is **complementary** to npm's own
223+
`--provenance` flag — that one is recorded inside the npm registry, the
224+
Sigstore attestation is recorded on GitHub. Both verify, neither
225+
replaces the other.
226+
227+
### Required permissions
228+
229+
Each publish job carries:
230+
231+
```yaml
232+
permissions:
233+
contents: read # (or write for pysdk to attach assets)
234+
id-token: write # OIDC token for Sigstore signing
235+
attestations: write # write to GitHub's attestations API
236+
```
237+
238+
### Verifying a release
239+
240+
Given a downloaded artifact (tarball, sdist, wheel):
241+
242+
```bash
243+
# npm package (CLI or MCP)
244+
npm pack @looptech-ai/understand-quickly-cli # downloads .tgz
245+
gh attestation verify ./looptech-ai-understand-quickly-cli-*.tgz \
246+
--owner looptech-ai
247+
248+
# PyPI artifact
249+
pip download --no-deps understand-quickly # downloads .whl + sdist
250+
gh attestation verify ./understand_quickly-*.whl \
251+
--owner looptech-ai
252+
gh attestation verify ./understand-quickly-*.tar.gz \
253+
--owner looptech-ai
254+
```
255+
256+
A successful verify proves: artifact digest matches a signed statement
257+
on Sigstore's Rekor log, the signing identity is a workflow in the
258+
`looptech-ai/understand-quickly` repo, and the workflow file matches one
259+
of our `publish-*.yml`.
260+
261+
### When attestation fails the build
262+
263+
The action fails the job (and therefore the publish) if:
264+
265+
- The `subject-path` glob matches **zero** files. Cause: the prior `pack`
266+
/ `build` step changed its output location or filename. Fix the glob.
267+
- The `id-token: write` permission is missing. Fix: re-add to the job
268+
permissions block.
269+
- Sigstore is unreachable. Rare; the action retries. If persistently
270+
failing, re-run the workflow.
271+
272+
There's no "skip on failure" — by design, a release that can't be
273+
attested doesn't ship.
274+
205275
## See also
206276

207277
- [`npm-org-setup.md`](npm-org-setup.md) — one-time npm org + token setup.
208278
- [`pypi-trusted-publishing.md`](pypi-trusted-publishing.md) — OIDC setup for PyPI.
209279
- [`../../CHANGELOG.md`](../../CHANGELOG.md) — human-curated changelog (release-please appends).
210280
- [release-please action docs](https://github.com/googleapis/release-please-action).
281+
- [GitHub artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds).

0 commit comments

Comments
 (0)