Skip to content

fix(publish): upload sidecar sigstore bundles from the reusable workflow#50

Open
vadimpiven wants to merge 1 commit intomainfrom
fix/upload-sidecar-bundles
Open

fix(publish): upload sidecar sigstore bundles from the reusable workflow#50
vadimpiven wants to merge 1 commit intomainfrom
fix/upload-sidecar-bundles

Conversation

@vadimpiven
Copy link
Copy Markdown
Owner

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).

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
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR fixes the missing sidecar sigstore bundle upload in publish.yaml by adding a bundle-dir input to attest-addons, writing per-addon bundle files named by basename(bundleUrl.pathname), and adding a gh release upload --clobber step with the required contents: write permission. The core logic is sound and well-tested.

  • The action SHA pinned in publish.yaml (311ea97…, v0.9.0) predates this PR's changes — bundles output will be empty until the follow-up SHA-bump commit is merged, causing the upload step to fail at gh release upload with no files. This is the documented two-commit pattern, but worth confirming the follow-up is queued.
  • basename(new URL(bundleUrl).pathname) has no collision guard: duplicate basenames silently overwrite each other's bundle file, leaving fewer release assets than expected.

Confidence Score: 5/5

Safe 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.

.github/workflows/publish.yaml — confirm the SHA-bump follow-up PR is planned. .github/actions/attest-addons/index.ts — consider adding a duplicate-basename guard.

Important Files Changed

Filename Overview
.github/actions/attest-addons/index.ts Adds bundle-dir input, writes per-addon bundle files named by basename(bundleUrl.pathname), and outputs a bundles JSON array; minor risk of silent filename collision if two bundleUrls share the same basename.
.github/workflows/publish.yaml Adds contents: write, passes bundle-dir to the action, and adds a gh release upload --clobber step; however the pinned action SHA (311ea97, v0.9.0) lacks bundle-dir/bundles support, so the upload step will fail until a follow-up SHA-bump commit is merged.
packages/node-addon-slsa/tests/attest-addons.test.ts New assertions verify bundle file count, filenames match basename(bundleUrl), and content identity across addons; logic is correct.
packages/node-addon-slsa/README.md Adds contents: write to the publish job example and explains the sidecar-bundle upload; accurate reflection of the workflow change.
.github/actions/attest-addons/dist/index.js Compiled bundle; correctly reflects the TypeScript source changes (reads bundle-dir, writes named files, emits bundles output).
Prompt To Fix All 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.

---

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Comment on lines +93 to 95
const filename = basename(new URL(bundleUrl).pathname);
const path = join(bundleDir, filename);
await writeFile(path, bundleJson);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant