fix(publish): upload sidecar sigstore bundles from the reusable workflow#50
fix(publish): upload sidecar sigstore bundles from the reusable workflow#50vadimpiven wants to merge 1 commit intomainfrom
Conversation
v0.9.0 moved provenance verification to sidecar sigstore bundles fetched from consumer-declared `bundleUrl` at install time, but the reusable `publish.yaml` never uploaded them — so verify-addons would 404 (barely masked by BUNDLE_FETCH_RETRY_DELAYS) and install-side `wget` would 404 permanently. The upload is conceptually the consumer's responsibility (their release), but the reusable workflow does it on their behalf using the caller's token. Changes: - publish.yaml: give `attest-addons` a `bundle-dir` on $RUNNER_TEMP, add an "Upload sidecar bundles" step that reads the `bundles` output and runs `gh release upload` against $GITHUB_REPOSITORY / $GITHUB_REF_NAME, and bump the job's permissions to `contents: write`. - attest-addons: write each bundle with basename = final path segment of `bundleUrl`, so `gh release upload <path>` drops the asset at the filename the URL promises. - README: mirror the permissions bump and explain the sidecar upload. - tests: assert filenames match `basename(bundleUrl)`. Caller-visible breaking change since v0.9.x: consumers must grant the `publish` job `permissions: contents: write` (previously only needed on the build job for the `.node.gz` upload). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Code Review
This pull request updates the attest-addons action to use the basename of the bundleUrl for sigstore bundle filenames, ensuring compatibility with release uploads. It also updates the README and tests to reflect these changes and the requirement for contents: write permissions. Feedback suggests decoding the URL pathname to handle percent-encoded characters and validating the extracted filename to prevent runtime errors.
| const records = await Promise.all( | ||
| hashed.map(async ({ platform, arch, url, bundleUrl, sha256 }) => { | ||
| const filename = `${platform}-${arch}-${sha256}.sigstore`; | ||
| const filename = basename(new URL(bundleUrl).pathname); |
There was a problem hiding this comment.
The bundleUrl pathname should be decoded before extracting the basename. If the URL contains percent-encoded characters (such as @ in scoped packages or spaces), basename will return the encoded string. This leads to a filename mismatch on disk and a subsequent 404 when the consumer tries to fetch the asset using the original URL, as the uploaded asset name will literally contain the percent signs. Additionally, it is safer to verify that a valid filename was extracted to avoid runtime errors during file operations.
const filename = basename(decodeURIComponent(new URL(bundleUrl).pathname));
if (!filename) {
throw new Error(`Could not determine filename from bundleUrl: ${bundleUrl}`);
}
Greptile SummaryThis PR fixes the missing sidecar sigstore bundle upload in
Confidence Score: 5/5Safe to merge — both findings are P2 and the core implementation is correct. All remaining findings are P2: the SHA-pin gap is the acknowledged two-commit pattern and the filename-collision scenario requires contrived inputs. No P0/P1 issues found.
Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .github/workflows/publish.yaml
Line: 68
Comment:
**SHA still pins to old action without `bundle-dir`/`bundles` support**
The action referenced here (`311ea97...`, v0.9.0) predates this PR — it doesn't accept `bundle-dir` or emit a `bundles` output. Until the follow-up SHA-bump commit, `${{ steps.attest.outputs.bundles }}` evaluates to an empty string. `jq` then receives a blank heredoc, exits non-zero (silently, because process substitution doesn't propagate to `set -e`), `mapfile` populates an empty array, and `gh release upload "$TAG"` is invoked with no files — failing at that point. This is the stated two-commit pattern, but as merged this PR leaves `publish.yaml` broken until the SHA is updated to this commit.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: .github/actions/attest-addons/index.ts
Line: 93-95
Comment:
**Silent overwrite when two `bundleUrl`s share the same basename**
If any two entries produce the same `basename(new URL(bundleUrl).pathname)` (e.g., both end in `bundle.sigstore`), the second `writeFile` silently clobbers the first. The `records` array still carries both entries with the duplicate `path`, so the upload step passes the same filesystem path twice to `gh release upload`; with `--clobber` that upload succeeds, but only one release asset exists under that filename. Consider throwing when a collision is detected:
```typescript
const filename = basename(new URL(bundleUrl).pathname);
const path = join(bundleDir, filename);
if (seen.has(filename)) {
throw new Error(`bundleUrl basename collision: "${filename}" appears more than once`);
}
seen.add(filename);
await writeFile(path, bundleJson);
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(publish): upload sidecar sigstore bu..." | Re-trigger Greptile |
| @@ -67,9 +68,28 @@ jobs: | |||
| uses: "vadimpiven/node-addon-slsa/.github/actions/attest-addons@311ea9782fd47be1b9c1c221c14bb9469f56402f" # v0.9.0 | |||
There was a problem hiding this comment.
SHA still pins to old action without
bundle-dir/bundles support
The action referenced here (311ea97..., v0.9.0) predates this PR — it doesn't accept bundle-dir or emit a bundles output. Until the follow-up SHA-bump commit, ${{ steps.attest.outputs.bundles }} evaluates to an empty string. jq then receives a blank heredoc, exits non-zero (silently, because process substitution doesn't propagate to set -e), mapfile populates an empty array, and gh release upload "$TAG" is invoked with no files — failing at that point. This is the stated two-commit pattern, but as merged this PR leaves publish.yaml broken until the SHA is updated to this commit.
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/publish.yaml
Line: 68
Comment:
**SHA still pins to old action without `bundle-dir`/`bundles` support**
The action referenced here (`311ea97...`, v0.9.0) predates this PR — it doesn't accept `bundle-dir` or emit a `bundles` output. Until the follow-up SHA-bump commit, `${{ steps.attest.outputs.bundles }}` evaluates to an empty string. `jq` then receives a blank heredoc, exits non-zero (silently, because process substitution doesn't propagate to `set -e`), `mapfile` populates an empty array, and `gh release upload "$TAG"` is invoked with no files — failing at that point. This is the stated two-commit pattern, but as merged this PR leaves `publish.yaml` broken until the SHA is updated to this commit.
How can I resolve this? If you propose a fix, please make it concise.| const filename = basename(new URL(bundleUrl).pathname); | ||
| const path = join(bundleDir, filename); | ||
| await writeFile(path, bundleJson); |
There was a problem hiding this comment.
Silent overwrite when two
bundleUrls share the same basename
If any two entries produce the same basename(new URL(bundleUrl).pathname) (e.g., both end in bundle.sigstore), the second writeFile silently clobbers the first. The records array still carries both entries with the duplicate path, so the upload step passes the same filesystem path twice to gh release upload; with --clobber that upload succeeds, but only one release asset exists under that filename. Consider throwing when a collision is detected:
const filename = basename(new URL(bundleUrl).pathname);
const path = join(bundleDir, filename);
if (seen.has(filename)) {
throw new Error(`bundleUrl basename collision: "${filename}" appears more than once`);
}
seen.add(filename);
await writeFile(path, bundleJson);Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/actions/attest-addons/index.ts
Line: 93-95
Comment:
**Silent overwrite when two `bundleUrl`s share the same basename**
If any two entries produce the same `basename(new URL(bundleUrl).pathname)` (e.g., both end in `bundle.sigstore`), the second `writeFile` silently clobbers the first. The `records` array still carries both entries with the duplicate `path`, so the upload step passes the same filesystem path twice to `gh release upload`; with `--clobber` that upload succeeds, but only one release asset exists under that filename. Consider throwing when a collision is detected:
```typescript
const filename = basename(new URL(bundleUrl).pathname);
const path = join(bundleDir, filename);
if (seen.has(filename)) {
throw new Error(`bundleUrl basename collision: "${filename}" appears more than once`);
}
seen.add(filename);
await writeFile(path, bundleJson);
```
How can I resolve this? If you propose a fix, please make it concise.
v0.9.0 moved provenance verification to sidecar sigstore bundles fetched from consumer-declared
bundleUrlat install time, but the reusablepublish.yamlnever uploaded them — so verify-addons would 404 (barely masked by BUNDLE_FETCH_RETRY_DELAYS) and install-sidewgetwould 404 permanently. The upload is conceptually the consumer's responsibility (their release), but the reusable workflow does it on their behalf using the caller's token.Changes:
attest-addonsabundle-diron $RUNNER_TEMP, add an "Upload sidecar bundles" step that reads thebundlesoutput and runsgh release uploadagainst $GITHUB_REPOSITORY / $GITHUB_REF_NAME, and bump the job's permissions tocontents: write.bundleUrl, sogh release upload <path>drops the asset at the filename the URL promises.basename(bundleUrl).Caller-visible breaking change since v0.9.x: consumers must grant the
publishjobpermissions: contents: write(previously only needed on the build job for the.node.gzupload).