Skip to content

publish: add --provenance (npm provenance via Sigstore keyless signing) - #30522

Open
robobun wants to merge 1 commit into
mainfrom
farm/753daa17/npm-provenance
Open

publish: add --provenance (npm provenance via Sigstore keyless signing)#30522
robobun wants to merge 1 commit into
mainfrom
farm/753daa17/npm-provenance

Conversation

@robobun

@robobun robobun commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #15601
Fixes #18611

What

Adds bun publish --provenance / --no-provenance / --provenance-file=<path>, matching npm publish --provenance.

When run inside GitHub Actions (with id-token: write) or GitLab CI (with SIGSTORE_ID_TOKEN), bun publish --provenance --access public will:

  1. Fetch an OIDC identity token from the CI environment
  2. Generate an ephemeral ECDSA P-256 keypair and sign the token's subject as proof-of-possession
  3. Request a short-lived signing cert from Fulcio (https://fulcio.sigstore.dev)
  4. Build an SLSA provenance in-toto statement from the CI env vars, wrap it in a DSSE envelope, sign it
  5. Upload an intoto entry to Rekor (https://rekor.sigstore.dev)
  6. Attach the resulting Sigstore bundle to the registry PUT body under _attachments["{name}-{version}.sigstore"]

NPM_CONFIG_PROVENANCE=true is honored (what actions/setup-node sets).

Why not sigstore-rs?

The sigstore crate (https://github.com/sigstore/sigstore-rs) was evaluated:

  • It only supports hashedrekord signing (bundle messageSignature), not DSSE attestation (bundle dsseEnvelope) — which is what npm provenance requires.
  • Its IdentityToken hard-requires an email claim, which GitHub Actions OIDC tokens don't carry.
  • Its sign/fulcio/rekor features pull reqwest + tokio + aws-lc-rs, duplicating bun's own HTTP (bun_http) and TLS (BoringSSL) stacks.

So the new src/sigstore/ crate uses the same RustCrypto primitives sigstore-rs does (p256, pkcs8) for the ephemeral key + signature, but speaks the Fulcio/Rekor protocol directly over bun_http::AsyncHTTP::send_sync() and assembles the bundle JSON with serde_json. The wire format is matched against sigstore-js (what npm actually uses).

SLSA predicate

Matches libnpmpublish/lib/provenance.js exactly — same env var set, same build-type URIs, same statement shape:

  • GitHub Actions → in-toto Statement v1 / SLSA provenance v1 (buildDefinition/runDetails)
  • GitLab CI → in-toto Statement v0.1 / SLSA provenance v0.2 (invocation/materials)

Side fix: as _ inference vs serde_json

Linking serde_json into bun_runtime exposes its impl PartialEq<Value> for i32/u16/… blanket. That makes existing err.errno == E::EEXIST as _ patterns in node_fs.rs, win_watcher.rs, and options_jsc.rs fail E0282_ now has two candidate RHS types (u16 and serde_json::Value). Those sites are fixed to use explicit as u16 / as i32, which is strictly more robust regardless of this change.

Testing

test/cli/install/bun-publish-provenance.test.ts stands up a single Bun.serve that mocks the GitHub OIDC token endpoint, Fulcio /api/v2/signingCert, Rekor /api/v1/log/entries, and the npm registry PUT. Asserts on:

  • Fulcio request shape (OIDC token, SPKI pubkey PEM, proof-of-possession)
  • Rekor intoto entry shape (double-base64'd payload, envelope/payload hashes)
  • Registry body _attachments["*.sigstore"] contains a valid bundle with the right mediaType, dsseEnvelope, SLSA v1 predicate, tlogEntries decoded from the Rekor mock
  • Preflight errors: unsupported CI, GHA without id-token: write, missing --access public, NPM_CONFIG_PROVENANCE + --no-provenance override, --provenance-file subject mismatch

All 8 tests pass with the change, all 8 fail on system bun (--provenance is unrecognized).

Override endpoints (for testing / private sigstore)

  • BUN_SIGSTORE_FULCIO_URL (default https://fulcio.sigstore.dev)
  • BUN_SIGSTORE_REKOR_URL (default https://rekor.sigstore.dev)
  • BUN_SIGSTORE_TLOG_BASE_URL (default https://search.sigstore.dev/)
  • BUN_SIGSTORE_OIDC_AUDIENCE (default sigstore)

Rebase notes (onto b9ef885)

The branch was squashed to one commit before rebasing 1329 commits of main; conflicts and follow-on fixes:

  • publish_command.rs: main changed pub(crate) fn publish to private and write!(..).ok() to let _ = write!(..); took main's style, kept the provenance code. Main also removed integrity from Context, so the subject digest is now computed from ctx.tarball_bytes in maybe_generate_provenance (verified by the --provenance-file test, which packs a real tarball and matches its digest).
  • bun_http::AsyncHTTP: init_sync no longer takes the response buffer (it moved to send_sync(&mut buf)), send_sync returns owned metadata, and status_code is a method; http_json updated. Because the metadata is now owned and dropped, the clone_metadata LSAN suppression this PR previously added is gone (verified: the suite passes under the CI LSAN env without it), so test/leaksan.supp is untouched.
  • New clippy bans on main (str::find/contains/splitn/lines, chunks_exact with a constant) converted to bun_core::strings / as_chunks.
  • pack_command.rs: manager became ctx.manager in the surrounding code.
  • Test: stderrForInstall was removed from the harness (bunEnv now suppresses the warning it filtered); the helper reads stdout/stderr/exit concurrently.
  • Cargo.lock regenerated from main's; the added package set is unchanged.
  • Multi-line comments under src/ compressed to single lines or removed (comment-cop).

Known follow-ups (out of scope for this PR)

  • bun publish does not honor HTTPS_PROXY / HTTP_PROXY / NO_PROXY: all four pre-existing AsyncHTTP::init_sync call sites in publish_command.rs (registry GET, PUT, OTP retry, web-login poll) pass None for http_proxy, and the three new Sigstore calls in bun_sigstore::http_json follow that pattern. Threading env_loader::get_http_proxy_for through all seven (as install's NetworkTask already does) is a self-contained follow-up with its own test surface; fixing only the sigstore subset here would not make the self-hosted-runner-behind-proxy scenario work end-to-end.

@robobun

robobun commented May 11, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 12:54 PM PT - Aug 14th, 2026

@robobun, your commit 7726110 is building: #96513

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. Implement bun publish --provenance as in npm #15601 - Directly implements the requested bun publish --provenance feature with Sigstore keyless signing, Fulcio cert issuance, and Rekor transparency log upload
  2. Prevent publishing when publishConfig.provenance set to true in package.json #18611 - With provenance now supported, publishConfig.provenance: true in package.json no longer needs to be silently ignored or error — packages can be published with provenance as expected

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #15601
Fixes #18611

🤖 Generated with Claude Code

Comment thread src/runtime/cli/publish_command.rs Outdated
Comment thread src/runtime/cli/publish_command.rs Outdated
Comment thread src/sigstore/provenance.rs Outdated
Comment thread src/sigstore/lib.rs
Comment thread src/install/PackageManager/CommandLineArguments.rs Outdated
Comment thread test/cli/install/bun-publish-provenance.test.ts Outdated
Comment thread src/install/PackageManager/CommandLineArguments.rs Outdated
Comment thread src/runtime/cli/publish_command.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All my earlier nits have been addressed and I didn't find anything new, but this is a ~1.3k-line cryptographic/supply-chain feature (new bun_sigstore crate, p256/ecdsa deps, Fulcio/Rekor protocol, OIDC token handling) that deserves a human pass on the protocol shape and dependency additions.

Extended reasoning...

Overview

This PR implements bun publish --provenance (npm provenance via Sigstore keyless signing), closing #15601. It introduces a new src/sigstore/ crate (~1000 LOC) that speaks the Fulcio/Rekor protocol directly over bun_http, generates SLSA in-toto statements from CI env vars, builds DSSE envelopes signed with an ephemeral P-256 key, and embeds the resulting Sigstore bundle in the registry PUT body. It also wires --provenance / --no-provenance / --provenance-file through CLI parsing and PublishConfig, adds docs, adds ~460 lines of integration tests against a mock Fulcio/Rekor/registry, and includes incidental as _as u16/i32 fixes in node_fs.rs / win_watcher.rs / options_jsc.rs exposed by linking serde_json into bun_runtime. New transitive deps land in Cargo.lock (p256, ecdsa, elliptic-curve, pkcs8, der, sec1, sha2, rfc6979, getrandom 0.2, etc.) and the x64 baseline-verify allowlists gain entries for sha2's SHA-NI path and a memchr count_raw symbol.

Security risks

This is squarely security-sensitive: it handles OIDC identity tokens (read from ACTIONS_ID_TOKEN_REQUEST_TOKEN / SIGSTORE_ID_TOKEN and sent as Bearer auth to Fulcio), generates ephemeral ECDSA keypairs via rand_core::OsRng, signs DSSE PAE encodings, parses PEM certificates by hand, hand-builds the canonical-JSON envelope hash for Rekor, and constructs the bundle that registries will verify for supply-chain provenance. The --provenance-file path deliberately skips full sigstore.verify() and only checks subject name/digest, deferring chain/tlog verification to the registry — a documented divergence from npm. The http_json helper Box::leaks request buffers (bounded to ≤3 calls per publish, so not a practical leak, but worth a glance). None of this looks wrong to me, but it's exactly the class of code where a second human pair of eyes on the wire-format fidelity vs. sigstore-js and the dependency surface is appropriate.

Level of scrutiny

High. This is a new user-facing feature implementing a cryptographic attestation protocol from scratch (rather than via sigstore-rs, for reasons documented in the PR description and Cargo.toml), pulling in ~20 new transitive crates, and producing artifacts that downstream consumers will trust for supply-chain integrity. The author has been very responsive — every nit I raised across four review rounds was either fixed or convincingly rebutted (the extract_jwt_subject behavior matches current sigstore-js post-#1485, not the older issuer-switch). But correctness of the bundle/tlog wire shapes and the decision to hand-roll vs. depend on sigstore-rs are design calls a maintainer should sign off on.

Other factors

Test coverage is solid for the happy path and preflight errors (mock OIDC/Fulcio/Rekor/registry server, asserts on request shapes and bundle contents). The musl build failure flagged by robobun was against an earlier commit (5708e5d) and predates the baseline-allowlist additions in 4c24826; CI status on HEAD (2f0a2fa) should be confirmed. The incidental as _ fixes are strictly more robust and low-risk.

Jarred-Sumner pushed a commit that referenced this pull request May 12, 2026
…0527)

## WebKit changes (88b2f7a2 → 5488984d)

Single commit on top of the previous pin:

### `module-loader: don't double-fire moduleRegistryModuleSettled after
inline sync replay` (oven-sh/WebKit#225, rebased #217)

**File:** `Source/JavaScriptCore/runtime/JSMicrotask.cpp`

**What:** Adds a `modulePromise->status() != Pending` early-return guard
to `moduleRegistryModuleSettled`, symmetric with the guard already
present in `moduleRegistryFetchSettled`. Gated under `#if
USE(BUN_JSC_ADDITIONS)`.

**Why:** `require()` of an ESM whose graph contained a diamond
dependency through a barrel deadlocked (release) / aborted on `ASSERTION
FAILED: m_status == Status::Fetching` (debug).
`hostLoadImportedModule`'s synchronous-replay branch (taken when
`require(esm)` is draining the synchronous module queue) calls
`fetchComplete` + fulfills `modulePromise` inline. If a
`ModuleRegistryFetchSettled` reaction had already run on the *normal*
microtask queue for the same entry before sync mode was entered, it left
a stale `ModuleRegistryModuleSettled` reaction queued there. When the
normal queue later drained, that reaction re-entered `fetchComplete` on
an already-`Fetched` entry.

No changes to `JSType.h`. No WebCore code-generator changes.

---

**Verification:**
- ✅ `test/regression/issue/30493.test.ts` fails on current `main`
(assertion crash, empty stdout)
- ✅ Same test passes on `bun run build:local` with the patched WebKit
- ✅ Same test passes on `bun bd` with the prebuilt preview tarball
- ✅ Full bun CI green against `autobuild-preview-pr-225-2b6b1c39` (build
#53556 — 67 pass, 3 pre-existing main flakes also red on #30522)

Fixes #30493
Fixes #30281
Closes #30283 (the dependency-free 6-file repro in this PR covers the
same root cause without needing a react+MUI install)

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
@robobun
robobun force-pushed the farm/753daa17/npm-provenance branch from 2f0a2fa to 6ed659b Compare May 12, 2026 15:27
Comment thread src/install/PackageManager/PackageManagerOptions.rs Outdated
Comment thread src/runtime/cli/publish_command.rs Outdated
Comment thread docs/pm/cli/publish.mdx
Comment thread src/install/PackageManager/CommandLineArguments.rs Outdated
Comment thread src/sigstore/lib.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No further issues from me — all prior comments are addressed. Deferring to a human given this is a hand-rolled Sigstore signing implementation with new crypto deps (p256/ecdsa/pkcs8), and the musl build failures in #53767 still need a look.

Extended reasoning...

Overview

This PR adds bun publish --provenance by introducing a new src/sigstore/ crate (~1000 LOC) that implements Sigstore keyless signing end-to-end: OIDC token fetch, ephemeral ECDSA P-256 keypair generation, Fulcio cert issuance, DSSE envelope construction, Rekor tlog upload, and Sigstore bundle assembly. It wires this into publish_command.rs, adds CLI flags, reads publishConfig.provenance from package.json, honors NPM_CONFIG_PROVENANCE, and ships docs + an integration test that mocks Fulcio/Rekor/OIDC/registry. It also adds ~20 transitive crates to Cargo.lock (p256, ecdsa, elliptic-curve, der, spki, sec1, rfc6979, sha2, etc.) and includes a side-fix for as _ inference ambiguity in node_fs.rs / win_watcher.rs / options_jsc.rs triggered by linking serde_json.

Security risks

This is supply-chain security code by definition. It hand-implements a cryptographic attestation protocol (rather than using sigstore-rs, for documented reasons), handles OIDC bearer tokens, signs with ephemeral keys, and constructs the bundle that the npm registry uses to display provenance badges. The implementation is closely modeled on sigstore-js / libnpmpublish and is well-tested against mocks, but the wire-format correctness against real Fulcio/Rekor and the npm registry's verifier hasn't been demonstrated in CI. The new RustCrypto deps (p256, pkcs8, getrandom 0.2) are reputable but expand the dependency surface.

Level of scrutiny

High. New crate, new crypto deps, security-sensitive protocol implementation, and a documented design decision (bypass sigstore-rs, speak the protocol directly over bun_http) that a maintainer should ratify. This is well outside the "simple/mechanical" bar for auto-approval.

Other factors

I've reviewed this across several rounds; every inline comment I raised (dead parameter, .sigstore key stem, comment accuracy, mutual-exclusion check, lowercase npm_config_provenance, publishConfig.provenance wiring, == Some(true) vs .is_some(), docs gaps, error-prefix wording) has been addressed and resolved — the diff now looks clean to me. However, Build #53767 shows the aarch64-musl and x64-musl builds failing on this branch, which should be resolved before merge. The Windows fs-promises-writeFile-async-iterator failures appear unrelated (pre-existing flake referenced in the Cargo.toml profile comment).

@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status at 51979c4, rebased on main (b0fb1f7):

All review threads resolved. 8/8 tests pass locally on debug, release, and under the exact CI LSAN env; merge-base canary fails 8/8. cargo clippy and cargo miri test green in CI.

CI (build 63709): bun-publish-provenance.test.ts passes on every lane, clippy/miri/format green. The three failures are not this PR's:

  • test/js/node/zlib/zlib.test.js crash on debian-13-x64-asan: panics with "78 JavaScript functions were called outside of the microtask queue without draining microtasks. Use EventLoop.runCallback()", a debug-assertion in node:zlib callback dispatch. Introduced by main's node:stream v26.3.0 sync (899168a) together with debug-assertions now enabled on the asan lane (de73fda); the same crash appears on unrelated builds 63703 and 63707. This PR doesn't touch zlib, streams, or the event loop.
  • test/integration/next-pages/test/dev-server-ssr-100.test.ts on darwin-26-aarch64: puppeteer failed to download chrome-headless-shell from the CDN. Network/infra.
  • Lint JavaScript GH action: oxlint: command not found (exit 127). The workflow's lint binary wasn't on PATH; infra.

This rebase (104 commits): one test/leaksan.supp conflict (unioned both suppression blocks); 51979c4 hoists setDefaultTimeout to module scope (was a no-op inside beforeAll; same class as main's 1432988) and fixes the comment-drift that hoist caused. Ready for maintainer review of the Sigstore protocol implementation.


What this PR does: implements bun publish --provenance / --no-provenance / --provenance-file matching npm's libnpmpublish wire format: the full Sigstore keyless flow (OIDC, ephemeral P-256, Fulcio /api/v2/signingCert, DSSE, Rekor /api/v1/log/entries, bundle v0.2) in a new bun_sigstore crate over bun_http, attached as _attachments["{name}-{version}.sigstore"] on the registry PUT. Honors publishConfig.provenance and NPM_CONFIG_PROVENANCE. Closes #15601, closes #18611.

How verified: 8 integration tests against in-process mock Fulcio/Rekor/OIDC/registry assert on the full bundle shape (DSSE envelope, SLSA v1 predicate, tlog entry, UTF-8 round-trip of the em-dash character in Rekor's signed note, length = JS string length).

@robobun
robobun force-pushed the farm/753daa17/npm-provenance branch from 59f7cc5 to 53e24b1 Compare May 12, 2026 21:17
Base automatically changed from claude/phase-a-port to main May 14, 2026 08:09
@robobun
robobun force-pushed the farm/753daa17/npm-provenance branch from 53e24b1 to 49773a4 Compare May 15, 2026 11:35
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR implements bun publish --provenance with Sigstore keyless signing for npm packages. It adds OIDC token acquisition, ephemeral key generation, Fulcio certificate signing, Rekor transparency log entry creation, and SLSA provenance statement generation for GitHub Actions and GitLab CI. The bundle is embedded into npm registry PUT requests. Includes CLI flags, config file support, documentation, comprehensive tests, and type casting improvements across the codebase.

Changes

Sigstore Provenance Publishing Feature

Layer / File(s) Summary
Workspace setup and sigstore crate initialization
Cargo.toml, src/sigstore/Cargo.toml, src/runtime/Cargo.toml
bun_sigstore registered as workspace member with path dependency. New crate manifest declares workspace-scoped metadata and adds serde, serde_json, p256, pkcs8, rand_core dependencies. Runtime crate wires sigstore dependency via workspace = true.
Sigstore core library and attestation flow
src/sigstore/lib.rs
Implements keyless npm provenance pipeline: fetches OIDC token from CI, extracts JWT subject with email verification fallback, generates ephemeral P-256 key, requests Fulcio x509 cert chain, constructs DSSE envelope with DSSEv1 pre-auth encoding, creates Rekor intoto log entry, assembles Sigstore v0.2 bundle JSON. Provides verify_bundle() for externally generated bundles, CI provider detection, and error handling with special formatting for usage errors.
SLSA provenance statement generation
src/sigstore/provenance.rs
Generates provider-specific SLSA statements: npm PURL construction with scope URL-encoding, subject arrays with package SHA-512 digests, GitHub Actions v1 predicates (buildDefinition, runDetails), and GitLab CI v0.1 SLSA v0.2 predicates (invocation parameters, environment, metadata, materials from CI env vars).
CLI arguments and publish configuration
src/install/PackageManager/CommandLineArguments.rs, src/install/PackageManager/PackageManagerOptions.rs
Adds --provenance, --no-provenance, --provenance-file <path> flags to bun publish. PublishConfig struct gains provenance: Option<bool> and provenance_file: &[u8] fields. Argument parser handles tri-state override with mutual-exclusion validation; Options::load populates settings when CLI flags provided.
Pack command provenance config reading
src/runtime/cli/pack_command.rs
When FOR_PUBLISH is enabled, reads publishConfig.provenance from package.json and stores into publish_config.provenance if unset, allowing package configuration to control provenance.
Publish command provenance integration
src/runtime/cli/publish_command.rs
Determines provenance enablement via precedence: --provenance-file (always enabled) → merged publish_config/CLI → NPM_CONFIG_PROVENANCE env. Validates CI support and --access public requirement. Generates or verifies bundle against tarball SHA-512. Embeds bundle into _attachments as {package}-{version}.sigstore with UTF-16 length computation matching npm encoding.
CLI documentation for provenance flags
docs/pm/cli/publish.mdx, docs/snippets/cli/publish.mdx
Documents --provenance and --no-provenance boolean flags, CI/permission requirements, publishConfig.provenance configuration, NPM_CONFIG_PROVENANCE implicit enabling, --provenance-file path option with subject digest matching constraint, mutual exclusivity, and Sigstore endpoint environment variable overrides.
Provenance integration test suite
test/cli/install/bun-publish-provenance.test.ts
End-to-end tests with mocked Sigstore: GitHub Actions success flow verifies OIDC/Fulcio/Rekor/registry traffic and bundle attachment content (subject, predicate fields, digest, verification material). Error tests cover unsupported CI, missing id-token permission, missing --access public. Precedence tests verify NPM_CONFIG_PROVENANCE, publishConfig.provenance, and CLI flag interactions. Bundle file tests verify subject digest matching and attachment size/content. Mutual-exclusivity tests check flag combinations.
Type casting improvements and baseline allowlist updates
src/runtime/dns_jsc/options_jsc.rs, src/runtime/node/node_fs.rs, src/runtime/node/win_watcher.rs, scripts/verify-baseline-static/allowlist-x64.txt, scripts/verify-baseline-static/allowlist-x64-windows.txt
Explicit u16 type casts for errno comparisons in address family conversion, cp error handling (async/sync Windows/non-Windows branches), and readlink NOENT detection to improve type inference clarity. Allowlist files updated with memchr AVX2 symbol count and new entries, plus new sha2 SHA-NI allowlist block with feature-gate and CPUID gating comments.

Suggested reviewers

  • Jarred-Sumner
  • alii
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly identifies the main feature addition: --provenance flag for npm provenance via Sigstore keyless signing.
Linked Issues check ✅ Passed PR fully implements requested features from both issues: #15601 (bun publish --provenance) and #18611 (provenance support prevents publish errors).
Out of Scope Changes check ✅ Passed Changes are all in-scope: new sigstore crate, publish command integration, CLI args, documentation, and type-inference fixes required by serde_json linking.
Description check ✅ Passed The description explains what the PR does and how it was verified, although it uses equivalent headings instead of the template headings.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/cli/publish_command.rs`:
- Around line 914-916: Add a typed env-var accessor for NPM_CONFIG_PROVENANCE in
src/bun_core/env_var.rs using the existing macro pattern (new!() or
platform_specific_new!() to handle "NPM_CONFIG_PROVENANCE" and
"npm_config_provenance" variants), give it a clear identifier like
NPM_CONFIG_PROVENANCE and ensure it exposes a .get() method; then update the
call site in publish_command.rs (replace the bun_core::getenv_z(...)
.or_else(...) block) to use the new accessor's .get() to retrieve the value
(preserving the same behavior for missing values).

In `@src/sigstore/Cargo.toml`:
- Around line 40-42: Update the rand_core dependency entry in Cargo.toml to pin
to a patched release: change the version specifier for rand_core from "0.6" to
"0.6.2" (keeping default-features = false and features = ["getrandom"]) so the
resolver will exclude vulnerable 0.6.0/0.6.1 releases; leave the p256 and pkcs8
entries unchanged.

In `@src/sigstore/lib.rs`:
- Around line 210-211: Replace the std::fs::read(...) call with
bun_sys::File-based I/O: open the provenance path using bun_sys::File (instead
of converting path bytes with String::from_utf8_lossy), read its contents into a
Vec<u8> (e.g., read_to_end) and assign to bytes, and map errors to
SigstoreError::Usage as before; when converting the incoming path bytes to a
platform-native path, use platform-appropriate OsString/OsStr conversion
(OsStrExt::from_bytes on Unix, wide-string conversion on Windows) so you never
rely on from_utf8_lossy and ensure the error mapping still includes the
underlying error.

In `@test/cli/install/bun-publish-provenance.test.ts`:
- Around line 481-490: The test currently asserts packProc.exitCode directly
which hides subprocess stderr; capture the stderr from the Bun.spawn'd process
(packProc) after awaiting packProc.exited (e.g., read packProc.stderr as text
into a variable like stderr) and immediately before
expect(packProc.exitCode).toBe(0) add a guarded failure check: if
(packProc.exitCode !== 0) { expect(stderr).toBe(""); } so that failing runs
surface the full stderr output for diagnostics while keeping the final expect on
packProc.exitCode; update references around the Bun.spawn call that creates
packProc and the tarballPath assertion accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44fde984-5499-48ea-8ad2-47c87a008423

📥 Commits

Reviewing files that changed from the base of the PR and between 314d044 and 3d2c2c7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • Cargo.toml
  • docs/pm/cli/publish.mdx
  • docs/snippets/cli/publish.mdx
  • scripts/verify-baseline-static/allowlist-x64-windows.txt
  • scripts/verify-baseline-static/allowlist-x64.txt
  • src/crash_handler/lib.rs
  • src/errno/lib.rs
  • src/install/PackageManager/CommandLineArguments.rs
  • src/install/PackageManager/PackageManagerOptions.rs
  • src/perf/tracy.rs
  • src/runtime/Cargo.toml
  • src/runtime/cli/Arguments.rs
  • src/runtime/cli/pack_command.rs
  • src/runtime/cli/publish_command.rs
  • src/runtime/cli/run_command.rs
  • src/runtime/cli/upgrade_command.rs
  • src/runtime/dns_jsc/options_jsc.rs
  • src/runtime/jsc_hooks.rs
  • src/runtime/node/node_fs.rs
  • src/runtime/node/win_watcher.rs
  • src/runtime/webview/ChromeProcess.rs
  • src/sigstore/Cargo.toml
  • src/sigstore/lib.rs
  • src/sigstore/provenance.rs
  • src/spawn/process.rs
  • src/spawn_sys/spawn_process.rs
  • test/cli/install/bun-publish-provenance.test.ts

Comment thread src/runtime/cli/publish_command.rs
Comment thread src/sigstore/Cargo.toml Outdated
Comment thread src/sigstore/lib.rs Outdated
Comment thread test/cli/install/bun-publish-provenance.test.ts
Comment thread src/install/PackageManager/CommandLineArguments.rs Outdated
Comment thread src/sigstore/lib.rs Outdated
Comment thread src/sigstore/lib.rs Outdated
Comment thread src/runtime/cli/publish_command.rs Outdated
Comment thread src/sigstore/Cargo.toml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/cli/publish_command.rs`:
- Around line 1091-1095: Move cheap provenance validations to run before the
dry-run early return: create a small preflight function (e.g.,
validate_provenance_preflight or split out a no-network branch) that checks the
conflicting flags (`--provenance` vs `--provenance-file`), ensures `--access
public` when required, validates CI support, and verifies the
`--provenance-file` input format; call this new validation from publish() before
the dry_run check. Keep the existing maybe_generate_provenance(ctx) (the
networked Sigstore bundle generation) after the dry-run return so the heavy,
networked behavior is not executed during dry-run. Ensure publish() uses
validate_provenance_preflight for quick checks and only invokes
maybe_generate_provenance for real generation when not dry-run.

In `@src/sigstore/lib.rs`:
- Around line 208-267: verify_bundle currently only parses dsseEnvelope.payload
and compares subjects without authenticating the bundle; update verify_bundle to
perform full provenance verification before returning LoadedBundle by (1)
validating the DSSE envelope signature (call a new or existing helper like
verify_dsse_envelope or the sigstore verifier on the parsed bundle), (2)
validating the signer certificate chain / Fulcio certificate (use a helper
validate_fulcio_chain or sigstore cert chain verifier) and (3) verifying the
Rekor entry/inclusion proof if present (verify_rekor_entry or equivalent). Only
after these checks succeed should verify_bundle return Ok(LoadedBundle { .. });
on any verification failure return a SigstoreError::Usage with a clear message.
Ensure you reference and use the parsed bundle, dsseEnvelope, and subject
variables already in verify_bundle.
- Around line 414-424: The current logic in the identity extraction returns an
error when a non-empty c.email exists but c.email_verified != Some(true);
instead, follow the docstring by falling back to c.sub in that case: change the
branch around c.email / c.email_verified so that if c.email is Some(non-empty)
and c.email_verified == Some(true) you return Ok(email), otherwise continue to
check c.sub (i.e., do not return Err(SigstoreError::Identity(...)) immediately)
and only return Err(SigstoreError::Identity("JWT subject not found".into())) if
c.sub is missing or empty.

In `@test/cli/install/bun-publish-provenance.test.ts`:
- Around line 2-15: Add a 5-minute default timeout by importing
setDefaultTimeout from "bun:test" and calling beforeAll(() =>
setDefaultTimeout(1000 * 60 * 5)); near the top of the file (alongside the
existing beforeAll/afterAll and the VerdaccioRegistry setup) so long-running
operations in this install CLI test (registry, Bun.serve mocks, tarball packing
and subprocesses) won't hit the default timeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c6ec1c9c-41dd-465b-8cd1-5d68cb2327dd

📥 Commits

Reviewing files that changed from the base of the PR and between 3d2c2c7 and 0acbe38.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • src/install/PackageManager/CommandLineArguments.rs
  • src/runtime/cli/publish_command.rs
  • src/sigstore/Cargo.toml
  • src/sigstore/lib.rs
  • test/cli/install/bun-publish-provenance.test.ts

Comment thread src/runtime/cli/publish_command.rs Outdated
Comment thread src/sigstore/lib.rs
Comment thread src/sigstore/lib.rs
Comment thread test/cli/install/bun-publish-provenance.test.ts Outdated
Comment thread scripts/verify-baseline-static/allowlist-x64.txt
Comment thread src/sigstore/lib.rs
Comment thread src/sigstore/lib.rs Outdated
Comment thread test/cli/install/bun-publish-provenance.test.ts
Comment thread src/sigstore/lib.rs Outdated
Comment on lines +37 to +39
// ──────────────────────────────────────────────────────────────────────────
// Public entry point
// ──────────────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +41 to +42
/// A serialized Sigstore bundle plus the bits the caller needs to report
/// on / attach to the publish body.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +52 to +55
/// Public-good Sigstore + npm defaults, matching sigstore-js and
/// `libnpmpublish/lib/provenance.js`. Overridable for testing against a
/// mock Fulcio/Rekor (e.g. a `Bun.serve` fixture) via the `BUN_SIGSTORE_*`
/// env vars.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +93 to +94
/// Sigstore bundle media type for the v0.2 wire format (uses
/// `x509CertificateChain` for verification material; npm accepts v0.2+).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +97 to +102
/// Sign an in-toto statement and produce a Sigstore bundle.
///
/// `payload` is the JSON-encoded in-toto statement (e.g. from
/// [`provenance::generate`]). The payload is *not* re-serialized — it is
/// embedded as-is in the DSSE envelope, so the caller controls the exact
/// byte representation the tlog records.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +189 to +190
/// Result of [`verify_bundle`] — a pre-built bundle ready to be
/// attached to the publish body.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +196 to +203
/// `--provenance-file` path: verify an externally-generated Sigstore bundle's
/// DSSE-envelope subject matches the package being published (name + sha512)
/// and return it for attachment. Ported from `libnpmpublish` `verifyProvenance`
/// — npm additionally runs `sigstore.verify()` over the bundle (chain + tlog);
/// we do the subject match only, leaving full verification to the registry.
///
/// The caller reads the file (via `bun_sys::File`, not `std::fs`) and passes
/// the bytes — keeps this crate I/O-free apart from the HTTP calls in [`attest`].

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +266 to +268
// ──────────────────────────────────────────────────────────────────────────
// OIDC identity — sigstore-js `CIContextProvider`
// ──────────────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +274 to +276
/// Lossy UTF-8 for env/wire bytes that land in JSON or error text. npm
/// reads the same values through Node's `process.env`, itself a lossy
/// UTF-8 decode, so U+FFFD replacement matches its behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +282 to +283
/// Which CI provider supplies the OIDC token. Drives the SLSA predicate
/// shape and the preflight error messages (matching npm's wording).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +291 to +292
/// Detect the provider from the environment, mirroring npm's
/// `ci-info` checks used in `libnpmpublish/lib/provenance.js`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +311 to +312
/// Preflight checks ported from `libnpmpublish` `ensureProvenanceGeneration`
/// — surfaces a precise error *before* we start talking to Fulcio.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +350 to +351
// GitHub Actions: GET $ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore with
// `Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +374 to +377
// `@actions/core` does `encodeURIComponent(audience)`; the value is
// user-settable via `BUN_SIGSTORE_OIDC_AUDIENCE`, so percent-encode it.
// `bun_core::strings::percent_encode_write` is a path-segment encoder
// (doesn't escape `& = + space`), so match `encodeURIComponent` directly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +411 to +412
/// Extract the subject to sign as proof-of-possession — sigstore-js
/// `oidc.extractJWTSubject`: `email` (if verified) else `sub`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +414 to +415
// `header.payload[.signature]` — the payload is the second segment; a
// token with only one `.` yields everything after it (unsigned JWT).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +428 to +434
// `Value`, not `bool`: Dex (the reference self-hosted Sigstore OIDC
// backend), Azure AD v1, and some Auth0 configs emit this as the
// *string* `"true"`. `Option<bool>` would fail the whole struct
// deserialize on that — sigstore-rs / sigstore-python both carry a
// string-or-bool shim for this field; sigstore-js (untyped
// `JSON.parse`) just sees `"true" === true` → false and falls
// through to `sub`. We match that behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +441 to +443
// sigstore-js: `claims.email_verified === true ? claims.email : claims.sub`
// — fall through to `sub` when email is present but unverified (or the
// claim is a non-`true` boolean / string / anything else).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +454 to +456
// ──────────────────────────────────────────────────────────────────────────
// Fulcio — sigstore-js `CAClient` / `external/fulcio.ts`
// ──────────────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +543 to +545
// ──────────────────────────────────────────────────────────────────────────
// Rekor — sigstore-js `toProposedIntotoEntry` + `TLogClient`
// ──────────────────────────────────────────────────────────────────────────

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +547 to +553
/// Build the Rekor `intoto` v0.0.2 proposed entry, POST it, and convert the
/// response into the serialized `tlogEntries[0]` object the bundle carries.
///
/// We use the legacy `intoto` kind (not `dsse`) — npm still submits `intoto`
/// by default via sigstore-js's `entryType: 'intoto'`, and the npm registry
/// accepts either, so matching keeps the bundle byte-for-byte diffable
/// against `npm publish --provenance` output.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +566 to +570
// Rekor's canonical DSSE-envelope hash — sigstore-js `calculateDSSEHash`:
// JSON-canonicalize `{payloadType,payload:b64,signatures:[{sig:b64,publicKey:<PEM>}]}`
// (keyid omitted when empty) and SHA-256 it. With no optional fields the
// key-sorted form is fixed, so a hand-written template suffices; keep it
// in sync if a `keyid` is ever threaded through here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +665 to +673
// `inclusionProof` is optional in the bundle format (older Rekor
// deployments omit it), but when Rekor *does* send one it must be
// well-formed: a partial hash chain or a dropped `rootHash` would
// produce a corrupt proof that fails verification. So: absent → None;
// present-but-malformed → hard error. `.filter(!is_null)` treats
// explicit `"inclusionProof": null` as absent (sigstore-js parity —
// `?.inclusionProof ? … : undefined`; public-good Rekor uses Go
// `omitempty` and never emits it, but private Rekor via
// `BUN_SIGSTORE_REKOR_URL` might).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Comment thread src/sigstore/lib.rs Outdated
Comment on lines +685 to +686
// `Option<Vec<_>>` collect — any bad element fails the
// whole proof rather than silently shrinking the chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you need a paragraph-long comment to justify why the workaround is OK, the code is wrong — fix the code

Implements --provenance / --no-provenance / --provenance-file for
bun publish, matching npm's libnpmpublish wire format: OIDC token from
CI, ephemeral P-256 key, Fulcio signing cert, DSSE envelope, Rekor
intoto entry, Sigstore bundle v0.2 attached as
_attachments["{name}-{version}.sigstore"] on the registry PUT. Honors
publishConfig.provenance and NPM_CONFIG_PROVENANCE. New bun_sigstore
crate over bun_http; SLSA predicates for GitHub Actions and GitLab CI.

Closes #15601
Closes #18611

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional findings (outside current diff — PR may have been updated during review):

  • 🟡 src/sigstore/lib.rs:942-950 — The local fn hex(bytes: &[u8]) -> String reimplements bun_core::fmt::bytes_to_hex_lower_string / bun_core::fmt::hex_lower(bytes).to_string() — the same helper this PR already uses at publish_command.rs (bun_fmt::hex_lower(&integrity).to_string()). bun_sigstore already depends on bun_core, so no new dep is needed; suggest dropping hex() and calling the in-tree helper. (hex_decode has no arbitrary-length equivalent in bun_core::fmt, so keeping that one is fine.)

    Extended reasoning...

    What the issue is

    src/sigstore/lib.rs defines a local helper in the "Misc small helpers" section:

    fn hex(bytes: &[u8]) -> String {
        const HEX: &[u8; 16] = b"0123456789abcdef";
        let mut s = String::with_capacity(bytes.len() * 2);
        for &b in bytes {
            s.push(HEX[(b >> 4) as usize] as char);
            s.push(HEX[(b & 0xF) as usize] as char);
        }
        s
    }

    This is byte-for-byte equivalent to two existing helpers in bun_core::fmt (src/bun_core/fmt.rs):

    • bytes_to_hex_lower_string(input: &[u8]) -> String at fmt.rs:2585 — allocating lowercase hex encode, exactly what hex() does.
    • hex_lower(bytes: &[u8]) -> HexBytes<'_, true> at fmt.rs:2622 — the Display adapter, so hex_lower(bytes).to_string() yields the same String.

    Why this fits REVIEW.md

    REVIEW.md "Code style & idioms reviewers enforce" says: "In runtime native code, grep for the in-tree helper before hand-writing anything. File I/O, paths, strings, hashing, formatting … Being the only file touching a raw primitive is a red flag." Hex encoding is exactly the formatting-primitive category that rule covers.

    Two facts make this concrete rather than hypothetical:

    1. The dep is already wired. src/sigstore/Cargo.toml lists bun_core.workspace = true, and lib.rs already imports from it (use bun_core::{MutableString, Output, ZStr, strings};). No new dependency is needed — just bun_core::fmt::hex_lower(...) (or add use bun_core::fmt as bun_fmt; to match the alias publish_command.rs uses).
    2. This same PR already uses the in-tree helper for the identical purpose. In src/runtime/cli/publish_command.rs (added in this PR), the SHA-512 tarball digest is hex-encoded via let sha512_hex = bun_fmt::hex_lower(&integrity).to_string(); — same input shape (a hash digest), same output (lowercase hex String). So the author demonstrably knows the helper exists and that it's fit for exactly this job; the local hex() in lib.rs is just an accidental duplicate.

    Step-by-step equivalence proof

    Take the two hex() call sites in rekor_create_intoto_entry:

    let payload_hash_hex = hex(&sha256(payload));
    // ...
    let envelope_hash_hex = hex(&sha256(canon.as_bytes()));

    For sha256(payload) = [0xc0, 0xff, 0xee, ...] (32 bytes):

    1. Local hex(): iterates each byte, pushes HEX[b>>4] then HEX[b&0xF] from b"0123456789abcdef""c0ffee..." (64 lowercase hex chars).
    2. bun_core::fmt::bytes_to_hex_lower_string() (fmt.rs:2585): allocates vec[0u8; 64], calls bytes_to_hex_lower which writes the same nibble table into it, then String::from_utf8_unchecked"c0ffee...".
    3. bun_core::fmt::hex_lower(bytes).to_string(): the HexBytes<'_, true> Display impl formats each byte as two lowercase hex chars → "c0ffee...".

    All three produce identical output for every possible &[u8] input. Replacing the two call sites with either in-tree helper is a pure no-op at the wire-format level — the Rekor hash.value / payloadHash.value fields carry the same string.

    What about hex_decode?

    The neighboring fn hex_decode(s: &str) -> Option<Vec<u8>> (used for Rekor's logID / rootHash / hashes) does not have an arbitrary-length equivalent in bun_core::fmt — the closest, parse_hex*, targets fixed-width integer types. So keeping the local hex_decode is correct; only hex() (the encoder) is a duplicate.

    Impact

    Zero functional impact. Both implementations produce identical lowercase hex; the Rekor entry, bundle JSON, and test assertions are unchanged. This is purely a house-convention nit under the "grep for the in-tree helper before hand-writing" rule — filed as nit, non-blocking.

    How to fix

    Replace the two call sites and drop the local helper:

    let payload_hash_hex = bun_core::fmt::hex_lower(&sha256(payload)).to_string();
    // ...
    let envelope_hash_hex = bun_core::fmt::hex_lower(&sha256(canon.as_bytes())).to_string();

    (or bun_core::fmt::bytes_to_hex_lower_string(&sha256(...)) — either matches what publish_command.rs already does in this PR.) Then delete fn hex(...).

Comment on lines +137 to +157
const env = {
...bunEnv,
// Pretend we're in GitHub Actions with id-token: write.
GITHUB_ACTIONS: "true",
ACTIONS_ID_TOKEN_REQUEST_URL: `${base}/gha-oidc`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: "gha-req-tok",
GITHUB_REPOSITORY: "oven-sh/bun",
GITHUB_SERVER_URL: "https://github.com",
GITHUB_WORKFLOW_REF: "oven-sh/bun/.github/workflows/release.yml@refs/heads/main",
GITHUB_REF: "refs/heads/main",
GITHUB_SHA: "deadbeef",
GITHUB_EVENT_NAME: "push",
GITHUB_REPOSITORY_ID: "1",
GITHUB_REPOSITORY_OWNER_ID: "2",
GITHUB_RUN_ID: "99",
GITHUB_RUN_ATTEMPT: "1",
RUNNER_ENVIRONMENT: "github-hosted",
// Point sigstore at our mock.
BUN_SIGSTORE_FULCIO_URL: base,
BUN_SIGSTORE_REKOR_URL: base,
CI: "1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 The GHA happy-path test spreads bunEnv (which spreads process.env) but never unsets SIGSTORE_ID_TOKENfetch_identity_token() checks that var first and returns it directly, so an ambient value (e.g. running the suite inside a GitLab job with id_tokens, or a stray cosign export) short-circuits the mocked /gha-oidc fetch and fails expect(oidcUrl).not.toBeNull() / the fakeJwt() assertion. The sibling tests already unset GITHUB_ACTIONS/GITLAB_CI for hermeticity; add SIGSTORE_ID_TOKEN: undefined, to this env object. Bun's own CI is BuildKite so this won't fire there — narrow nit.

Extended reasoning...

What the issue is

The GitHub Actions happy-path test ("attaches a sigstore bundle built against mock Fulcio/Rekor") builds its subprocess environment as:

const env = {
  ...bunEnv,
  GITHUB_ACTIONS: "true",
  ACTIONS_ID_TOKEN_REQUEST_URL: `${base}/gha-oidc`,
  ACTIONS_ID_TOKEN_REQUEST_TOKEN: "gha-req-tok",
  GITHUB_REPOSITORY: "oven-sh/bun",
  // ... GITHUB_* predicate vars, BUN_SIGSTORE_* endpoint overrides ...
};

bunEnv (test/harness.ts) spreads ...process.env and does not scrub SIGSTORE_ID_TOKEN. The env object above overrides GITHUB_ACTIONS, ACTIONS_ID_TOKEN_REQUEST_URL, ACTIONS_ID_TOKEN_REQUEST_TOKEN, and the GITHUB_*/BUN_SIGSTORE_* vars — but never sets SIGSTORE_ID_TOKEN: undefined. So if the ambient environment carries a non-empty SIGSTORE_ID_TOKEN, it flows through to the child process.

Why that changes behavior

fetch_identity_token() in src/sigstore/lib.rs checks SIGSTORE_ID_TOKEN before the GitHub Actions ACTIONS_ID_TOKEN_REQUEST_URL fetch:

fn fetch_identity_token(audience: &str) -> Result<String, SigstoreError> {
    // cosign-compatible env override — also how GitLab supplies its token.
    if let Some(tok) = env(bun_core::zstr!("SIGSTORE_ID_TOKEN")) {
        return Ok(utf8_lossy(tok).into_owned());
    }
    // ... only falls through to ACTIONS_ID_TOKEN_REQUEST_URL fetch here ...

The env() helper only filters empty strings, so any non-empty ambient value short-circuits the function. ensure_provenance_generation() (the preflight, which the test does control via GITHUB_ACTIONS: "true") is a separate function and doesn't gate on SIGSTORE_ID_TOKEN in the GitHub arm, so the preflight passes and the short-circuit only happens later at token-fetch time.

Step-by-step proof

Suppose the suite is run in an environment where SIGSTORE_ID_TOKEN=eyJ... is set — e.g. a contributor running bun bd test inside a GitLab CI job that has id_tokens: { SIGSTORE_ID_TOKEN: { aud: sigstore } } configured, or a shell with a leftover export SIGSTORE_ID_TOKEN=... from cosign:

  1. bunEnv spreads process.env, carrying SIGSTORE_ID_TOKEN into env.
  2. The child bun publish --provenance --access public runs ensure_provenance_generation() → sees GITHUB_ACTIONS=true and ACTIONS_ID_TOKEN_REQUEST_URL set → returns Ok(GithubActions). Preflight passes.
  3. attest() calls fetch_identity_token("sigstore")env("SIGSTORE_ID_TOKEN") is Some(<ambient>) → returns the ambient token immediately. The mock /gha-oidc handler is never hit, so oidcUrl stays null.
  4. The Fulcio POST proceeds with credentials.oidcIdentityToken = <ambient token>, not fakeJwt().
  5. expect(oidcUrl).not.toBeNull() fails; expect(fulcioReq.credentials.oidcIdentityToken).toBe(fakeJwt()) also fails.

Why nothing catches this

REVIEW.md's "Tests must be hermetic" and "Copy harness conventions exactly — spread bunEnv when modifying it ({...bunEnv, KEY: undefined})" both apply. The other tests in this same file demonstrate the intended pattern — GITHUB_ACTIONS: undefined, GITLAB_CI: undefined appear in the "errors outside of supported CI", "NPM_CONFIG_PROVENANCE", and "publishConfig.provenance" tests. SIGSTORE_ID_TOKEN was missed because it's read in fetch_identity_token(), not in the ensure_provenance_generation() preflight the author was reasoning about when picking which vars to override. grep SIGSTORE_ID_TOKEN test/ returns nothing.

Impact

Narrow. Bun's own CI is BuildKite (harness.ts isBuildKite), which doesn't set SIGSTORE_ID_TOKEN, so this won't fire in the project's CI. It only affects contributors running the suite in an environment where SIGSTORE_ID_TOKEN happens to be set — plausible (GitLab CI with id_tokens, or a cosign env export) but uncommon. Filed as nit, non-blocking.

How to fix

One line — add SIGSTORE_ID_TOKEN: undefined, to the env object (and optionally to the other GHA-mode tests for consistency):

const env = {
  ...bunEnv,
  SIGSTORE_ID_TOKEN: undefined,
  GITHUB_ACTIONS: "true",
  ACTIONS_ID_TOKEN_REQUEST_URL: `${base}/gha-oidc`,
  // ...
};

Comment on lines +84 to +85
describe("--provenance", () => {
test("attaches a sigstore bundle built against mock Fulcio/Rekor (GitHub Actions)", async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 The GitLab CI provenance path has zero test coverage — every test in this file either sets GITHUB_ACTIONS: "true" or explicitly unsets both providers, so CiProvider::GitlabCi, the SIGSTORE_ID_TOKEN early-return in fetch_identity_token(), the GitLab preflight-error arm, and the entire gitlab_statement() SLSA-v0.2 predicate builder (~75 CI_* env-var reads, a completely different in-toto/SLSA schema) are never executed. Per REVIEW.md "Cover the variant matrix", the GHA end-to-end test could be duplicated with a GitLab env fixture (GITLAB_CI: "true", SIGSTORE_ID_TOKEN: fakeJwt(), a handful of CI_* vars) asserting stmt._type === "https://in-toto.io/Statement/v0.1" / predicateType === "https://slsa.dev/provenance/v0.2" and the invocation.parameters / materials shape, plus a GITLAB_CI-without-SIGSTORE_ID_TOKEN preflight-error test. Nit — coverage gap only; the more-common GHA path is well-tested.

Extended reasoning...

What the gap is

GitLab CI is documented as one of exactly two first-class --provenance providers — in docs/pm/cli/publish.mdx:129, docs/snippets/cli/publish.mdx:92, and the CLI --provenance help string in CommandLineArguments.rs — and this PR ships ~150 lines of GitLab-specific code:

  • CiProvider::GitlabCi and its detect() branch (src/sigstore/lib.rs);
  • the SIGSTORE_ID_TOKEN early-return in fetch_identity_token() (lib.rs:314-316) — the only token path GitLab uses (no OIDC HTTP fetch);
  • the GitLab preflight-error arm in ensure_provenance_generation() (lib.rs:296-304);
  • the entire gitlab_statement() predicate builder (src/sigstore/provenance.rs:132-280) — a completely different schema from GitHub's (in-toto v0.1 / SLSA v0.2 with invocation.configSource/parameters/environment/materials, vs GitHub's in-toto v1 / SLSA v1 with buildDefinition/runDetails), plus the ci_params! macro reading ~75 distinct zstr!("CI_*") env-var literals.

None of it is exercised by bun-publish-provenance.test.ts.

Step-by-step proof

Walking the 8 tests in the file:

  1. "attaches a sigstore bundle … (GitHub Actions)" — sets GITHUB_ACTIONS: "true". GitHub path.
  2. "errors outside of supported CI" — sets GITHUB_ACTIONS: undefined, GITLAB_CI: undefined. CiProvider::detect() returns None.
  3. "errors in GitHub Actions without id-token permission" — sets GITHUB_ACTIONS: "true". GitHub preflight error.
  4. "requires --access public" — sets GITHUB_ACTIONS: "true". Fails before ensure_provenance_generation().
  5. "NPM_CONFIG_PROVENANCE=true …" — sets GITHUB_ACTIONS: undefined, GITLAB_CI: undefined. Unsupported-CI path.
  6. "publishConfig.provenance: true …" — sets GITHUB_ACTIONS: undefined, GITLAB_CI: undefined. Unsupported-CI path.
  7. "--provenance-file: …" — bare bunEnv. Skips CI detection entirely (--provenance-file short-circuits before ensure_provenance_generation()).
  8. "--provenance and --provenance-file are mutually exclusive" — bare bunEnv. Fails on mutual-exclusion / bundle validation before CI detection.

Grepping the file: GITLAB_CI appears only as GITLAB_CI: undefined; SIGSTORE_ID_TOKEN never appears at all. So no test ever reaches CiProvider::GitlabCi, and gitlab_statement() is never called. A typo in any of the ~75 zstr!("CI_*") string literals, or a shape mismatch in the json!({...}) predicate (e.g. invocation.configSource.digest.sha1 vs sha256), would ship silently.

Why this fits the repo's review bar

REVIEW.md is explicit on both counts:

  • "Cover the variant matrix, not just the repro. Every sibling entry point receiving the same fix (CLI flag AND JS API), both states of every flag …" — GitHub Actions and GitLab CI are the two arms of the CiProvider enum, and only one arm is tested.
  • "Every behavioral change ships an automated test in the same PR"gitlab_statement() is ~150 lines of new behavior producing a wire format consumed by external verifiers (npm registry, sigstore verify), with no test.

The shared Fulcio/Rekor/bundle-assembly plumbing is exercised via the GHA test, so this is scoped to the GitLab-specific bits: provider detection, the SIGSTORE_ID_TOKEN token path, the preflight error, and the SLSA-v0.2 predicate shape.

Impact

Coverage gap only — no known incorrect behavior. The GitHub Actions path (the far more common real-world use) is thoroughly tested end-to-end, and gitlab_statement() is a straightforward port of libnpmpublish/lib/provenance.js. But if any of the ~75 hard-coded CI_* literals is misspelled, or if the SLSA-v0.2 nesting is wrong, GitLab users would get either a rejected publish or a bundle that fails downstream verification, and CI would not catch it.

How to fix

Duplicate the GHA end-to-end test with a GitLab env fixture — the same Bun.serve mock works, since SIGSTORE_ID_TOKEN short-circuits the OIDC HTTP fetch:

const env = {
  ...bunEnv,
  GITLAB_CI: "true",
  SIGSTORE_ID_TOKEN: fakeJwt(),           // early-return in fetch_identity_token()
  CI: "true",
  CI_PROJECT_URL: "https://gitlab.com/oven-sh/bun",
  CI_COMMIT_SHA: "deadbeef",
  CI_JOB_NAME: "publish",
  CI_JOB_URL: "https://gitlab.com/oven-sh/bun/-/jobs/99",
  CI_JOB_ID: "99",
  CI_PIPELINE_ID: "1",
  CI_RUNNER_ID: "42",
  CI_CONFIG_PATH: ".gitlab-ci.yml",
  CI_PROJECT_PATH: "oven-sh/bun",
  BUN_SIGSTORE_FULCIO_URL: base,
  BUN_SIGSTORE_REKOR_URL: base,
};
// …
expect(stmt._type).toBe("https://in-toto.io/Statement/v0.1");
expect(stmt.predicateType).toBe("https://slsa.dev/provenance/v0.2");
expect(stmt.predicate.buildType).toBe("https://github.com/npm/cli/gitlab/v0alpha1");
expect(stmt.predicate.invocation.configSource.entryPoint).toBe("publish");
expect(stmt.predicate.invocation.parameters.CI_PROJECT_PATH).toBe("oven-sh/bun");
expect(stmt.predicate.invocation.parameters.CI_PAGES_URL).toBeUndefined(); // unset → omitted
expect(stmt.predicate.materials[0].digest.sha1).toBe("deadbeef");

Plus a preflight-error test: GITLAB_CI: "true" without SIGSTORE_ID_TOKENexpect(err).toContain('"SIGSTORE_ID_TOKEN"').

Filed as nit — the GHA path is well-covered and this is a variant-matrix coverage gap, not a demonstrated bug.

Comment on lines +100 to +104
oidcUrl = req.url;
expect(url.searchParams.get("audience")).toBe("sigstore");
expect(req.headers.get("authorization")).toBe("Bearer gha-req-tok");
return Response.json({ value: fakeJwt() });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 The two expect() calls inside the Bun.serve fetch handler (audience + Authorization header) violate REVIEW.md's "never throw inside event callbacks": if either fails, Bun.serve swallows the throw into a 500 and the test surfaces expect(out + err).toContain("Signed provenance statement") / expect(err).not.toContain("error:") instead of the real mismatch. Suggest capturing url.searchParams.get("audience") and req.headers.get("authorization") into locals (as fulcioReq/rekorReq/putBody already are) and asserting on them after await publish(...). Diagnostics-only nit — the test still fails on regression.

Extended reasoning...

What the issue is

The GHA happy-path test ("attaches a sigstore bundle built against mock Fulcio/Rekor") calls expect() directly inside the mock server's fetch handler:

using server = Bun.serve({
  port: 0,
  async fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/gha-oidc") {
      oidcUrl = req.url;
      expect(url.searchParams.get("audience")).toBe("sigstore");
      expect(req.headers.get("authorization")).toBe("Bearer gha-req-tok");
      return Response.json({ value: fakeJwt() });
    }
    ...

REVIEW.md's "Tests reviewers reject" section calls out both halves of this pattern: "Wire EVERY failure event … to reject the awaited promise — never throw inside event callbacks" and "Hunt vacuous patterns: … expects inside catch blocks or callbacks that may never fire". A Bun.serve fetch handler is exactly such a callback — the throw is caught by Bun.serve's error handling and converted into an HTTP 500 response rather than propagating to the test runner.

Step-by-step proof

Suppose a regression causes bun publish to send ?audience=sigstore-dev (or drops the Bearer prefix on the Authorization header):

  1. The child bun publish --provenance --access public calls fetch_identity_token("sigstore")http_json(GET, "http://localhost:PORT/gha-oidc?audience=sigstore-dev", …).
  2. The mock fetch handler runs; oidcUrl = req.url captures the URL, then expect(url.searchParams.get("audience")).toBe("sigstore") throws a JestAssertionError.
  3. Bun.serve catches the throw and responds with 500 Internal Server Error (the default error handler); the assertion error message goes into the response body, not to the test runner.
  4. Back in the child, http_json sees status >= 400 and returns SigstoreError::Http { who: "GitHub Actions OIDC", detail: "HTTP 500: …" }.
  5. SigstoreError::print() writes error: failed to generate provenance: GitHub Actions OIDC: HTTP 500: … to the child's stderr; Global::crash() exits nonzero.
  6. Back in the test, await publish(...) returns { out, err, exitCode: 1 }. The first post-publish assertion is expect(out + err).toContain("Signed provenance statement")that is what fails, with a diff showing the child's stderr rather than Expected: "sigstore" / Received: "sigstore-dev".

So the test still fails on regression (this is not a false-pass), but the failure message points at "missing 'Signed provenance statement' in output" rather than the actual audience/authorization mismatch — degraded diagnostics only.

Why nothing catches this

The "may never fire" concern is partially covered — expect(oidcUrl).not.toBeNull() after publish() confirms the endpoint was hit at all — but the two in-handler expect()s themselves would silently not run if the OIDC fetch were skipped, and their thrown errors never reach the test runner when they do run.

The same test already uses the correct capture-then-assert pattern for the other three endpoints: fulcioReq = await req.json(), rekorReq = await req.json(), and putBody = await req.json() are all captured inside the handler and asserted on after await publish(...) returns. The two OIDC checks are the only assertions inside the handler, so they're inconsistent with the file's own established convention.

Impact

Test-diagnostics only. The test correctly fails when bun publish sends the wrong audience or Authorization header; it just fails on the wrong assertion with an obscured message. Filed as nit (non-blocking) — this doesn't cause incorrect behavior, a false pass, or flakiness.

How to fix

Match the sibling captures — replace the two in-handler expect()s with local captures and assert after publish():

let oidcAudience: string | null = null;
let oidcAuth: string | null = null;
...
if (url.pathname === "/gha-oidc") {
  oidcUrl = req.url;
  oidcAudience = url.searchParams.get("audience");
  oidcAuth = req.headers.get("authorization");
  return Response.json({ value: fakeJwt() });
}
...
// ── OIDC flow ──
expect(oidcUrl).not.toBeNull();
expect(oidcAudience).toBe("sigstore");
expect(oidcAuth).toBe("Bearer gha-req-tok");

Comment thread src/sigstore/lib.rs
Comment on lines +834 to +842
fn hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
s.push(HEX[(b >> 4) as usize] as char);
s.push(HEX[(b & 0xF) as usize] as char);
}
s
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 This hand-rolled fn hex() duplicates bun_core::fmt::hex_lower (src/bun_core/fmt.rs:2622), which this same PR already uses in publish_command.rs (bun_fmt::hex_lower(&integrity).to_string()) for the identical digest→lowercase-hex purpose. bun_sigstore already depends on bun_core, so the two call sites in rekor_create_intoto_entry can be bun_core::fmt::hex_lower(&sha256(...)).to_string() and this local helper deleted. Nit — no functional difference; per REVIEW.md "grep for the in-tree helper before hand-writing anything … formatting".

Extended reasoning...

What the issue is

src/sigstore/lib.rs:834-842 defines a private fn hex(bytes: &[u8]) -> String that hand-writes a lowercase hex encoder via a nybble table:

fn hex(bytes: &[u8]) -> String {
    const HEX: &[u8; 16] = b"0123456789abcdef";
    let mut s = String::with_capacity(bytes.len() * 2);
    for &b in bytes {
        s.push(HEX[(b >> 4) as usize] as char);
        s.push(HEX[(b & 0xF) as usize] as char);
    }
    s
}

But bun_core::fmt::hex_lower already exists at src/bun_core/fmt.rs:2622 as a Display adapter (HexBytes<'_, true>) that produces byte-identical output. bun_sigstore's own Cargo.toml (added in this PR) declares bun_core.workspace = true, so the helper is already in scope with no new dependency edge.

Why this fits the repo's review bar

REVIEW.md's "Code style & idioms reviewers enforce" section says: "In runtime native code, grep for the in-tree helper before hand-writing anything. File I/O, paths, strings, hashing, formatting, validation, spawning, timers — use the most specific existing helper: … bun_core strings/fmt/Output … Being the only file touching a raw primitive is a red flag." Lowercase hex encoding of a byte slice is squarely in "formatting", and bun_core::fmt is the named module.

The strongest signal that this is a duplicate rather than a deliberate choice: this same PR already uses the in-tree helper for the identical purpose. In src/runtime/cli/publish_command.rs (added by this PR), the SHA-512 tarball digest is formatted with:

let sha512_hex = bun_fmt::hex_lower(&integrity).to_string();

— same operation (hash digest → lowercase hex string), same output shape. So the PR's own code establishes the convention; the sigstore crate just didn't follow it.

Step-by-step proof of equivalence

  1. hex(&[0xde, 0xad, 0xbe, 0xef]) allocates a String::with_capacity(8), then for each byte pushes HEX[hi_nybble] and HEX[lo_nybble] from b"0123456789abcdef""deadbeef".
  2. bun_core::fmt::hex_lower(&[0xde, 0xad, 0xbe, 0xef]) returns HexBytes<'_, true>(&[0xde, ...]); its Display impl walks the slice writing two lowercase hex chars per byte. .to_string() allocates and returns "deadbeef".
  3. Both produce lowercase, no separators, no 0x prefix — the exact wire format Rekor's spec.content.hash.value and spec.content.payloadHash.value fields expect (verified by the test's expect(rekorReq.spec.content.payloadHash.algorithm).toBe("sha256") assertions, and by publish_command.rs already using hex_lower for the sha512 subject digest that Rekor also validates).

So the substitution is byte-for-byte equivalent on the wire.

Where it's called

Two call sites, both in rekor_create_intoto_entry():

  • let payload_hash_hex = hex(&sha256(payload));
  • let envelope_hash_hex = hex(&sha256(canon.as_bytes()));

Both take a [u8; 32] SHA-256 output.

Impact

None functionally — the hand-rolled encoder is correct. This is purely a code-duplication / convention nit in new code, filed as nit (non-blocking).

How to fix

// delete fn hex() at lines 834-842, then:
let payload_hash_hex = bun_core::fmt::hex_lower(&sha256(payload)).to_string();
// ...
let envelope_hash_hex = bun_core::fmt::hex_lower(&sha256(canon.as_bytes())).to_string();

(or add use bun_core::fmt as bun_fmt; at the top to match publish_command.rs's spelling).

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prevent publishing when publishConfig.provenance set to true in package.json Implement bun publish --provenance as in npm

1 participant