Skip to content

install: warn when a registry manifest advertises an integrity hash bun cannot use - #38484

Open
robobun wants to merge 3 commits into
mainfrom
farm/ecd974a0/npm-manifest-malformed-shasum
Open

install: warn when a registry manifest advertises an integrity hash bun cannot use#38484
robobun wants to merge 3 commits into
mainfrom
farm/ecd974a0/npm-manifest-malformed-shasum

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • Flagged by the new defaulted_failure lint in the mordant bump (Bump mordant to 3bcf116 #38460): src/install/npm.rs built a version's integrity with Integrity::parse_sha_sum(shasum_str).unwrap_or_default().
  • parse_sha_sum rejects a dist.shasum that is not hex or has an odd length (src/install/integrity.rs:51). The unwrap_or_default() turned that rejection into an Integrity with Tag::UNKNOWN, which every verification path reads as "nothing to verify" (Streaming::verify returns true for an unsupported tag).
  • The dist.integrity field six lines above had the same outcome by a different route: Integrity::parse reports a malformed value or an unsupported algorithm as Tag::UNKNOWN rather than an error, so with no shasum to fall back on it went equally quiet.
  • Net effect: a manifest that advertised a hash bun could not use installed that version unverified and wrote "" into bun.lock, with nothing on stderr. A well-formed hash that does not match the bytes already fails the install, so the unusable case was the only one that went quiet.

Fix

  • PackageManifest::parse now tries dist.integrity, then dist.shasum (matching on the Result), and breaks out as soon as one yields a supported hash. If it reaches the end having been given a non-empty integrity or a malformed shasum, it adds one warning for that version to the manifest task's log, which run_tasks prints when the task resolves: warn: Unsupported or malformed integrity hash in registry metadata for pkg@1.0.0; its tarball will not be verified. A manifest that advertises no hash at all stays silent, as before.
  • The stored value is unchanged in every case (an unusable hash still ends up as the zeroed UNKNOWN value), so the binary manifest cache and lockfile output are byte-identical to before; the only new behavior is the warning.
  • A warning rather than an error matches how bun.lock.rs already treats an unparseable integrity string in the lockfile. Propagating the error would fail the whole manifest and make every version of the package uninstallable because one version's metadata is bad. The warning is per version, at manifest parse time, so it names the offending versions even when a different version ends up installed. The public registry always serves sha512 SRI or 40 hex chars, so it never fires there; in this repo only minimum-release-age.test.ts serves placeholder values (sha512-fake1==), and it still passes (49 tests) since it does not assert on the absence of warnings.
  • Removes the defaulted_failure:src/install/npm.rs entry from mordant-baseline.toml; it was the only finding of that lint in the file.
  • Verified with test/cli/install/bun-install-tarball-integrity.test.ts ("tarball integrity metadata forms"). Three new cases (malformed shasum, unsupported integrity algorithm, malformed integrity plus empty shasum) fail on the released build, where stderr is just the progress lines, and pass with this change. Three guards pin the surrounding behavior: a mismatching well-formed shasum still fails the install with no warning, an unusable integrity backed by a valid shasum installs with a sha1- pin and no warning, and a manifest with no hash installs silently. The whole file (22 tests), minimum-release-age.test.ts, and bun-install-streaming-extract.test.ts pass on the debug build.
  • install: fail closed on unusable npm integrity (pin computed sha512; reject invalid manifest/lockfile hashes) #31327 proposes the broader fail-closed policy (refuse unparseable manifest hashes, pin a computed sha512 when none is advertised). This change only makes the existing behavior visible and does not conflict with that direction.

Background

  • PackageManifest::parse (src/install/npm.rs) turns a registry packument into bun's binary manifest. For each version it reads dist.integrity (an SRI string such as sha512-...) and, when that is absent or unsupported, falls back to dist.shasum, the legacy 40 character hex sha1 that older packages carry instead.
  • Integrity is a tag plus digest bytes; Tag::UNKNOWN means "no hash available". Every verification path skips versions tagged UNKNOWN, which is why a silent parse failure is equivalent to switching verification off for that version.
  • Manifest parsing runs on a thread pool worker with a per-task Log; run_tasks prints that log on the main thread when the task completes, so warnings added during parsing show up in the normal bun install output and respect the usual log level flags.
  • mordant-baseline.toml is the ratchet for the mordant lint pack: it records how many findings of each lint per file are accepted as pre-existing, and CI fails a PR only when a file exceeds its count. Removing an entry means CI now requires the file to have zero findings of that lint.
Earlier shape of this PR

The first revision only matched on parse_sha_sum and warned about a malformed shasum ("Malformed shasum in registry metadata for ..."). Review pointed out that an unusable dist.integrity with no shasum reached the same silent outcome, so the warning now covers both fields with one message per version.


[review] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-install-tarball-integrity.test.ts
bun test v1.4.0 (0f7781825)

test/cli/install/bun-install-tarball-integrity.test.ts:
(pass) tarball integrity > should store integrity hash for local tarball in text lockfile [312.40ms]
(pass) tarball integrity > should store integrity hash for tarball URL in text lockfile [394.64ms]
(pass) tarball integrity > should install successfully from text lockfile without integrity hash (backward compat) [392.90ms]
(pass) tarball integrity > should add integrity hash to lockfile when re-resolving tarball dep [204.44ms]
(pass) tarball integrity > should fail integrity check when tarball URL content changes [564.00ms]
(pass) tarball integrity > should store consistent integrity hash for tarball URL across reinstalls [731.83ms]
(pass) tarball integrity > should install successfully from text lockfile without integrity hash for local tarball (backward compat) [259.59ms]
(pass) tarball integrity mismatch (hoisted) > should fail (not hang) when tarball bytes don't match manifest SHA-512 [351.51m
... (truncated)

release without fix: 3 FAILED
bun test v1.4.0-canary.1 (12fba92bf)

test/cli/install/bun-install-tarball-integrity.test.ts:
819 |       env: { ...env, BUN_INSTALL_CACHE_DIR: join(String(dir), ".cache") },
820 |       stdout: "pipe",
821 |       stderr: "pipe",
822 |     });
823 |     const [stderr, stdout, exitCode] = await Promise.all([proc.stderr.text(), proc.stdout.text(), proc.exited]);
824 |     expect(stderr).toContain(
                         ^
error: expect(received).toContain(expected)

Expected to contain: "warn: Unsupported or malformed integrity hash in registry metadata for pkg@1.0.0; its tarball will not be verified"
Received: "Resolving dependencies\nResolved, downloaded and extracted [4]\nSaved lockfile\n"

      at <anonymous> (/workspace/bun/test/cli/install/bun-install-tarball-integrity.test.ts:824:20)
819 |       env: { ...env, BUN_INSTALL_CACHE_DIR: join(String(dir), ".cache") },
820 |       stdout: "pipe",
821 |       stderr: "pipe",
822 |     });
823 |     const [stderr, stdout, exitCode] = await Promise.all([proc.stderr.text(), proc.stdout.text(), proc.exited]);
824 |     expect(stderr).toContain(
                         ^
error: expect(received).toContain(expected)

Ex
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/bun-install-tarball-integrity.test.ts
bun test v1.4.0 (0f7781825)

test/cli/install/bun-install-tarball-integrity.test.ts:
(pass) tarball integrity > should store integrity hash for local tarball in text lockfile [370.93ms]
(pass) tarball integrity > should store integrity hash for tarball URL in text lockfile [457.74ms]
(pass) tarball integrity > should install successfully from text lockfile without integrity hash (backward compat) [444.85ms]
(pass) tarball integrity > should add integrity hash to lockfile when re-resolving tarball dep [173.98ms]
(pass) tarball integrity > should store consistent integrity hash for tarball URL across reinstalls [609.39ms]
(pass) tarball integrity > should fail integrity check when tarball URL content changes [795.46ms]
(pass) tarball integrity > should install successfully from text lockfile without integrity hash for local tarball (backward compat) [314.29ms]
(pass) tarball integrity > should store consistent integrity hash for local tarball across reinstalls [506.56ms]
(pass) tarba
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 696ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/5] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

  nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19)

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output)
�[1m�[92m   Compiling�[0m bun_clap 
... (truncated)
diff hotspot
mordant-baseline.toml                              |   1 -
 src/install/npm.rs                                 |  32 ++++++-
 .../install/bun-install-tarball-integrity.test.ts  | 100 +++++++++++++++++++--
 3 files changed, 123 insertions(+), 10 deletions(-)

gate history · 2 passed · 0 rejected · iteration 0

evidence per changed file
file                                                    reads  edits  tests
mordant-baseline.toml                                       1      1      0
src/install/npm.rs                                          9      4      0
test/cli/install/bun-install-tarball-integrity.test.ts      4      8      0

A dist.shasum that failed to parse was replaced with an empty Integrity,
so the version installed with no verification and nothing said so. Log a
warning naming the package version instead, and drop the corresponding
mordant baseline entry.
@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 7:05 AM PT - Aug 14th, 2026

@robobun, your commit 0f77818 has some failures in Build #96000 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 38484

That installs a local version of the PR into your bun-38484 executable, so you can run:

bun-38484 --bun

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 19 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cbf91388-5d13-42e2-95b9-cabf79dff796

📥 Commits

Reviewing files that changed from the base of the PR and between 032b8db and 0f77818.

📒 Files selected for processing (3)
  • mordant-baseline.toml
  • src/install/npm.rs
  • test/cli/install/bun-install-tarball-integrity.test.ts

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review; CI is blocked only by macOS queue capacity.

Reproduced with the new cases in test/cli/install/bun-install-tarball-integrity.test.ts: a loopback registry serving a dist.shasum of 40 non-hex characters, or a dist.integrity bun cannot parse with no shasum. On the released build bun install prints only the progress lines, installs the package, and writes "" as its integrity in bun.lock; with this change it prints warn: Unsupported or malformed integrity hash in registry metadata for pkg@1.0.0; its tarball will not be verified and otherwise behaves the same. The baseline entry removed here was the only defaulted_failure finding in npm.rs; the mordant job on this PR passes with it removed.

Build 96000 on the current head: 177 of 179 jobs passed with no test failures (the annotated ones are retried flakes on unrelated lanes). The remaining 2 jobs are both :darwin: 14 aarch64 - test-bun and expired without ever getting an agent; that lane has a backlog of several hundred jobs across all PRs right now. Nothing in this change is platform specific. Retrying just those two jobs once the queue drains is enough; I have not pushed a retrigger since it would only requeue them into the same backlog.

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

LGTM — the inline nit is a pre-existing sibling gap, not a regression, and doesn't block.

Extended reasoning...

Overview

Replaces Integrity::parse_sha_sum(shasum_str).unwrap_or_default() in PackageManifest::parse (src/install/npm.rs:2609) with an explicit match that emits a warning to the manifest task's Log on Err. Removes the corresponding defaulted_failure entry from mordant-baseline.toml. Adds two tests to the existing tarball integrity metadata forms describe block and generalizes its serveManifest helper from integrity: string to dist: { integrity?, shasum? } (all three existing callers updated consistently).

Security risks

None introduced. The install-time behavior is unchanged: on parse failure the integrity remains Tag::UNKNOWN (skip verification) exactly as before — the only new effect is a stderr warning. On the Ok path the assignment is byte-identical. I checked the one non-obvious case: when dist.integrity was present-but-unsupported and dist.shasum is malformed, the old code overwrote package_version.integrity with Integrity::default() while the new code leaves the value assigned at line 2603 — but Integrity::parse already returns { tag: UNKNOWN } on every failure path, so the retained value is the same UNKNOWN default and there is no observable difference in verification or lockfile output.

Level of scrutiny

Low-to-medium. ~12 lines of Rust in a diagnostic-only path, following the exact log.add_warning_fmt(None, Loc::EMPTY, format_args!(...)) pattern already used throughout src/install/ (NetworkTask.rs, OverrideMap.rs, etc.). expected_name (fn parameter, &[u8]) and version_name (prop.key.slice()) are both in scope; bstr::BStr::new matches how the same file formats them at lines 2042/2085. The mordant baseline removal is correct — this was the only defaulted_failure finding in the file.

Other factors

Tests follow the file's conventions (local Bun.serve on port 0, await using, concurrent pipe drain, per-test cache dir). The malformed-shasum test asserts the exact warning string, exit 0, and that no sha*- pin lands in bun.lock; the mismatch test asserts the warning does NOT fire on a well-formed shasum. The PR description confirms both fail/pass appropriately under USE_SYSTEM_BUN=1 vs the debug build.

The inline nit (malformed dist.integrity with no usable shasum still installs silently) is a real adjacent gap per REVIEW.md's fix-the-whole-class guidance, but it's pre-existing, requires a different fix shape (Integrity::parse encodes failure in the return value rather than the type and can't distinguish malformed from unsupported-algorithm), and the PR already scopes the broader fail-closed question to #31327. Strict improvement over main; not blocking.

Comment thread src/install/npm.rs
…ages it

Integrity::parse reports failure through Tag::UNKNOWN rather than an
error, so a manifest whose integrity field is malformed or uses an
algorithm we do not support went just as quiet as the malformed shasum
case. Emit one warning per version whenever the registry advertised a
hash and none of it was usable; a manifest that advertises nothing
still installs silently as before.
Comment thread src/install/npm.rs Outdated
@robobun robobun changed the title install: warn when a registry manifest carries a malformed shasum install: warn when a registry manifest advertises an integrity hash bun cannot use Aug 14, 2026

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

LGTM — the follow-up in 7302b4b covers the sibling dist.integrity case I flagged, and 0f77818 drops the redundant comment.

Checked the 'integrity: block against each new test case and its negatives: unusable integrity salvaged by a valid shasum breaks out before the warning, an empty shasum hits Ok(UNKNOWN) and still warns via the non-empty integrity_str arm, and no-hash-at-all stays silent. On the Err(_) arm package_version.integrity is left at its default (Tag::UNKNOWN), so the stored value is byte-identical to before. Also confirmed the concurrent it.each reusing the integrity-unusable prefix is fine — tempDir uses mkdtempSync so each case gets a unique dir.

Extended reasoning...

Overview

Replaces Integrity::parse_sha_sum(shasum_str).unwrap_or_default() in PackageManifest::parse (src/install/npm.rs:2602-2636) with an explicit try-integrity-then-shasum block that emits one warning per version when the registry advertised a hash bun cannot use and neither field yielded a supported tag. Removes the corresponding defaulted_failure entry from mordant-baseline.toml. Adds six subprocess tests to the existing tarball integrity metadata forms describe block in test/cli/install/bun-install-tarball-integrity.test.ts (three that expect the warning, three guards that pin no-warning behavior for shasum-mismatch-still-fails, shasum-salvages-bad-integrity, and no-hash-advertised).

Security risks

None introduced. This is integrity-verification-adjacent, but the change is strictly additive visibility: the stored Integrity value on every path is byte-identical to before (verified against Integrity::default()Tag::UNKNOWN), so the binary manifest cache, lockfile output, and verification behavior are unchanged. The only new effect is a stderr warning for a case that was previously silent. No check is loosened.

Level of scrutiny

Low-medium. ~30 lines of Rust confined to one labeled block inside a per-version parsing loop, plus test additions. The public npm registry always serves sha512- SRI or 40-hex shasum, so the new path fires only against private/mirror registries with bad metadata. The one in-repo test that serves placeholder hashes (minimum-release-age.test.ts) is called out in the description as still passing. Traced each of the six new test cases through the block by hand and each hits the intended arm.

Other factors

My prior review pointed out the unhandled sibling (non-empty malformed dist.integrity with no/empty shasum); 7302b4b restructured the block to cover both fields under one condition (!integrity_str.is_empty() || malformed_shasum), and the new it.each covers exactly that case ({ integrity: "sha512-!!!", shasum: "" }). The comment-cop bot's feedback was addressed in 0f77818. Both threads are marked resolved. Tests follow harness conventions: describe.concurrent, Bun.serve({port: 0}), tempDir with using, Promise.all draining stdout/stderr/exited, Buffer.alloc(40, "x") over .repeat, exit-code asserted last. Integrity is Copy so reading integrity.tag after the assignment on line 2617-2618 is fine.

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.

2 participants