# Bun lockfile bun.lockb format: Threat model + CI checks [2026]

> bun.lockb made Bun installs fast, but it also changed how you audit dependency drift. Here’s what’s inside, what integrity it gives you, and how to enforce it in CI.

- Canonical: https://www.kunalganglani.com/blog/bun-lockfile-lockb-format
- Author: Kunal Ganglani
- Published: 2026-09-10 · Updated: 2026-09-10
- Category: Developer Tools · Tags: bun, javascript, lockfile, supply-chain-security, ci-cd

## TL;DR

bun.lockb is Bun’s older binary lockfile. It can make installs fast, but it also makes dependency changes harder to review, which is a real security problem. Bun now defaults to a text lockfile called bun.lock, which is much easier to diff in pull requests and enforce in CI. The key takeaway is simple: treat your lockfile like a security control, not a build artifact. Use frozen lockfile checks in CI, generate lockfiles without installing when you can, and snapshot your resolved dependency graph so you can spot unexpected changes over time.

## Bun Lockfile Internals: How `bun.lockb` Changes Your JavaScript Supply Chain Threat Model

Bun lockfile `bun.lockb` format is Bun’s older, binary representation of your fully-resolved dependency graph. It matters because lockfiles are the line between “we reviewed what we ship” and “we hope the registry gives us the same thing tomorrow.” Bun’s shift in v1.2 to a text lockfile (`bun.lock`) dragged an old tradeoff back into the open: speed and compactness vs. auditability when something goes sideways.

![black flat screen computer monitor](https://cdn.sanity.io/images/vzekdneq/production/099614c88f6c6917dbde4fa5669263c5c1cb1305-1200x675.webp)

I’m going to be blunt. Binary lockfiles are a security tax you pay later, with interest.

## What is `bun.lockb`, and why was it binary?

`bun.lockb` is Bun’s legacy binary lockfile. Like every lockfile, it freezes the exact versions (including transitive dependencies) that Bun resolved at a point in time so the next install can reproduce that graph.

![JavaScript code displayed on a dark screen with colorful syntax highlighting](https://cdn.sanity.io/images/vzekdneq/production/501d14642ca9092f769609996910d69110f94029-1200x675.webp)

Why binary? Because it’s fast to parse, smaller on disk, and easier for the package manager to treat as structured data instead of a big text blob. For a tool whose whole identity is “this should be faster,” that choice makes sense.

The catch is that format choices leak into team behavior:

- A **text lockfile** is reviewable by default. You can read it, diff it, grep it.
- A **binary lockfile** is opaque by default. Review turns into “trust the tool,” not “verify the change.”
So no, `bun.lockb` wasn’t just a performance detail. It quietly changed your team’s *supply chain operating model*.

Bun eventually conceded the point. When introducing Bun v1.2’s new lockfile, [Ashcon Partovi](https://bun.sh/blog/bun-v1.2) called out the “Problems with bun.lockb” and moved the default to a text-based `bun.lock`.

> If you can’t review a dependency change in a PR, you don’t have a dependency policy. You have a dependency vibe.

## Problems with `bun.lockb` (in real security workflows)

Most write-ups stop at “it’s not diffable.” True. Also incomplete.

![Lines of colorful JavaScript code displayed on a dark screen](https://cdn.sanity.io/images/vzekdneq/production/b7900d19c3ac3ee243cf4508bd7311a3758166ff-1200x675.webp)

The real problem is that `bun.lockb` breaks the stuff you rely on when you’re trying to keep a repo sane over years, across dozens of developers, under the occasional real-world incident.

### 1) PR review becomes useless for dependency drift

With a text lockfile, a reviewer can catch obvious weirdness in under a minute:

- a new dependency name that screams typosquat
- a jump from `1.2.3` to `9.0.0`
- a new Git URL dependency
- an unexpected registry host
With `bun.lockb`, the diff is basically “binary changed.” People stop looking. Not because they’re lazy. Because the workflow trains them that looking is pointless.

And when a supply-chain incident happens, the question is never philosophical. It’s practical: what changed, when, and how did it get past review?

### 2) Incident response gets slower and more annoying

With a text lockfile, you can answer the usual questions quickly:

- When did `left-pad` enter our dependency tree?
- Which PR introduced `event-stream@3.3.6`?
- Did we ever ship the compromised version?
With a binary lockfile, you end up reconstructing history by re-running tooling on old commits. That’s survivable, but it’s slower, more fragile, and it breaks in exactly the situations where you’re already under pressure.

### 3) “Integrity” turns into a vibes-based claim unless you define it

Engineers love saying “lockfiles give integrity guarantees” and then moving on. Don’t.

A lockfile can help with:

- **Reproducibility:** same inputs, same resolved versions.
- **Immutability checks:** verifying a downloaded tarball matches an expected hash.
A lockfile does *not* automatically protect you from:

- a malicious version that was legitimately published and then pinned
- compromised maintainer accounts
- compromised CI runners (your lockfile can be swapped)
- dependency confusion (you pin the wrong thing faster)
If you want a clean model, tie lockfiles to provenance. The boring but correct direction here is: treat dependency resolution as part of your build provenance story. If your org wants a shared language for that, the [SLSA framework](https://slsa.dev/) is still one of the few places the industry isn’t completely hand-wavy.

## Introducing `bun.lock`: what you gain when the lockfile is text

Bun v1.2 changed the default lockfile to a text-based `bun.lock` (replacing `bun.lockb` as the default). That’s a developer experience win, sure. But it’s also a security control.

Here’s what becomes possible when the file is readable:

- **Diffs become meaningful.** Reviewers can see what packages were added/removed and which versions moved.
- **Policy checks become easy.** CI can grep for banned patterns (Git URLs, untrusted registries, `link:` dependencies, whatever your org doesn’t allow).
- **Forensics get faster.** You can use `git blame` and `git log -p` like a normal adult without special tooling.
Ashcon also claims Bun got faster after the switch. Good. “Fast” and “auditable” don’t have to be enemies. We just keep shipping tooling that treats auditability as someone else’s problem.

If you want a practitioner breakdown of the performance angle, this video is decent:

[Watch: Bun.js Just Got WAY Faster (1.2.21  Update)](https://www.youtube.com/watch?v=ptT8mFkm7z8)

## Integrity guarantees: what Bun lockfiles provide (and what they don’t)

Bun’s docs mostly describe behavior and flags, not a formal threat model. So here’s the version I’d put in an internal security doc: what you actually get, and what you absolutely do not.

### What you get

1. **A pinned resolution of direct and transitive dependencies.** If `react@18.3.1` depends on `loose-envify@1.4.0`, that transitive pin is part of the lockfile.
1. **Deterministic installs when you enforce it.** Using `--frozen-lockfile` in CI gives you “don’t touch the lockfile” semantics.
1. **A reviewable artifact (with `bun.lock`).** This is the big one. Humans notice suspicious stuff scanners routinely miss.
### What you do not get

1. **No protection against picking the wrong dependency.** If someone adds `reaact` instead of `react`, the lockfile will faithfully pin the typo.
1. **No automatic protection against malicious-but-valid upstream publishes.** Lockfiles freeze reality, including bad reality.
1. **No provenance guarantee.** You still need controls around who can publish, how builds run, and what your CI runner can access.
If you want a complementary control, pair lockfile discipline with SBOMs and dependency graph monitoring. That’s the same reason I like writing down build and artifact hygiene in other ecosystems too, like in [Rust reproducible builds + SBOM + signed artifacts](/blog/rust-reproducible-builds-sbom).

## Generate a lockfile without installing (and why this matters in CI)

Bun supports generating or updating the lockfile without touching `node_modules`.

Per the official Bun docs, to generate a lockfile without installing dependencies into `node_modules`, use `--lockfile-only`:

- `bun install --lockfile-only` ([Bun lockfile docs](https://bun.sh/docs/install/lockfile))
This sounds like a minor flag. It’s not.

In CI, it lets you run a “resolution check” job that:

- runs fast
- avoids executing install scripts
- doesn’t mutate the workspace with a giant `node_modules`
There’s also a subtle behavior in the docs that matters for determinism: Bun **always saves the lockfile to disk even if it is already up to date**, except when `--frozen-lockfile` (or `--production`) is set.

That sentence is why I like a two-step posture:

1. **On developer machines:** allow lockfile updates.
1. **In CI:** prohibit lockfile updates and fail builds when drift exists.
If you care about supply-chain risk, the CI job that fails on lockfile drift is one of the highest-leverage controls you can add.

## `--frozen-lockfile` in Bun: what should fail the build?

`--frozen-lockfile` is the flag you use when you want Bun to treat the lockfile as an input, not an output.

The contract you want in CI is simple:

- If `package.json` changed but `bun.lock` didn’t, **fail**.
- If the lockfile would be updated (even if it’s “no functional change”), **fail**.
That’s how you stop silent dependency drift from sneaking into `main`.

This maps cleanly to how I think about compliance automation too. When I worked on SOC 2 scaffolding at Rise People, the lesson was painfully consistent: controls that happen after the fact become theatre. Controls baked into scaffolding and CI become defaults people actually follow.

### A concrete policy I like

- PRs can change app code freely.
- PRs that change dependency inputs (`package.json`) must change lockfile outputs (`bun.lock`) in the same PR.
- CI blocks any “lockfile regenerated by CI” behavior.
If you already have decent review hygiene, this philosophy will feel familiar from my other writing on review automation defaults in [7 safer defaults for code review automation](/blog/code-review-automation-defaults).

## How to review dependency changes safely when the lockfile is binary

If you’re stuck on `bun.lockb` (older Bun, legacy repo, or just inertia), you still need an auditable workflow.

Here are the only approaches I’ve seen that don’t fall apart in practice.

### Option A (best): migrate to `bun.lock`

Bun documents the migration path explicitly.

From the Bun docs “Text-based lockfile” section, migrate `bun.lockb` → `bun.lock` with:

1. Run `bun install --save-text-lockfile --frozen-lockfile --lockfile-only`
1. Delete `bun.lockb`
Source: [Bun docs](https://bun.sh/docs/install/lockfile#text-based-lockfile)

That command is doing three important things:

- `--save-text-lockfile`: output the new format
- `--frozen-lockfile`: don’t allow resolution changes during migration
- `--lockfile-only`: avoid side effects in `node_modules`
### Option B: normalize the dependency graph and diff that

Even with a binary lockfile, you can create a text artifact for review by snapshotting the resolved graph.

My rule is: **diff the graph, not the file**.

Practical ways to do this:

- generate an SBOM and diff it across commits
- dump a dependency list (direct + transitive) and diff it
- store the resolved graph as a CI artifact for every merge to `main`
This is the same “make it observable” move I use when I build lightweight tooling on this site. Maintaining 25+ free browser tools at [/tools](/tools) taught me that the thing that gets adopted is the thing people can actually see and grep.

### Option C: treat `bun.lockb` as an artifact, not a review surface

If you refuse to migrate, you can still improve your risk posture by:

- requiring two-person review for any `bun.lockb` change
- blocking lockfile changes from automation bots
- requiring a separate “dependency snapshot” file in PRs
Is it clunky? Yes.

Is it better than “binary changed”? Also yes.

## Automatic lockfile migration: supply-chain risk you didn’t ask for

Bun can automatically migrate other lockfiles when `bun.lock` is missing. Per the Bun docs, if there’s no `bun.lock`, Bun will migrate:

- `yarn.lock` (v1)
- `package-lock.json` (npm lockfileVersion 2, 3, or 4)
- `pnpm-lock.yaml`
…and it preserves the original lockfile.

Source: [Bun lockfile docs](https://bun.sh/docs/install/lockfile)

This is great for developer experience. It can be terrible for security if you don’t control it.

Here’s the failure mode:

1. A repo historically used npm/yarn/pnpm.
1. Someone runs `bun install` without noticing.
1. Bun migrates the lockfile.
1. The lockfile *format* changes, and reviewers miss the *graph* changes.
Even if Bun’s migration is correct, you’re now reviewing a massive rewrite of your dependency manifest. That’s the exact moment typosquats and supply-chain surprises slip in.

My recommendation:

- Make lockfile migration an explicit PR with a dedicated reviewer.
- Generate a dependency graph snapshot *before* and *after* migration.
- Block registry host changes unless explicitly approved.
If you want to go deeper on supply-chain attack patterns (dependency confusion, typosquatting), I’ve already written about the JavaScript ecosystem’s unique pain here: [NPM supply chain attacks in 2026](/blog/npm-supply-chain-attack-defense).

## bun.lock vs bun.lockb vs npm/yarn/pnpm: what to commit, and why

You should commit Bun’s lockfile to git. Bun’s docs say this bluntly: “Should it be committed to git? Yes.” (Bun docs)

The more useful question is which lockfile you want to be the source of truth.

Here’s a comparison table I’d actually use in a team decision:

| Lockfile | Format | Diff-friendly in PRs | Best use case | Supply-chain review posture |
| --- | --- | --- | --- | --- |
| `bun.lockb` | Binary | No | Legacy Bun installs, speed-first workflows | Weak unless you add separate graph snapshots |
| `bun.lock` | Text | Yes | Modern Bun (v1.2+) | Strong. Easy to review and enforce in CI |
| `package-lock.json` | JSON text | Sort of (noisy) | npm-native repos | OK. Reviewable but can be verbose |
| `yarn.lock` (v1) | Text | Yes | Yarn classic repos | Strong diff story; mature workflow |
| `pnpm-lock.yaml` | YAML text | Yes | pnpm repos | Strong diff story; strict by design |

Two concrete numbers that matter here:

- Bun’s text lockfile became the default starting in **Bun v1.2**.
- Bun’s docs explicitly call out it currently migrates npm lockfileVersion **2/3/4**, but not npm 6’s lockfileVersion **1**.
Those little version footnotes are exactly the kind of thing that turns into a bad day if you miss it during a migration.

## CI policy checks I’d add for Bun (today)

Actionable CI patterns, ordered by impact. No ceremony.

1. **Fail builds on lockfile drift**
  - Run `bun install --frozen-lockfile` in CI.
  - If Bun tries to change the lockfile, the job fails.
1. **Resolve without installing (faster, safer)**
  - Run `bun install --lockfile-only` in a separate job to validate resolution.
  - Pair with `--frozen-lockfile` when you want strictness.
1. **Block unreviewed lockfile changes**
  - Require CODEOWNERS approval when `bun.lock` changes.
  - If you’re still on `bun.lockb`, require a dependency snapshot artifact in the PR.
1. **Enforce registry and scope policies**
  - Maintain an allowlist of registries.
  - Reject dependencies that resolve from unexpected hosts.
1. **Pin or ban Git dependencies**
  - If you allow `git+https://...`, require commit SHAs, not branches.
1. **Monitor dependency graph deltas over time**
  - Store a normalized dependency list artifact for every merge to `main`.
  - Alert on additions of new top-level packages or large transitive expansions.
If you want inspiration for “policy as code,” I’d steal from how I think about [AI security](/blog/ai-security-complete-guide) and making unsafe states hard to enter. Same idea. Different threat model.

## How to monitor unexpected dependency graph changes over time

Most teams do dependency review at PR time and then never look back. That’s not enough.

The workflow I like:

- On every merge to `main`, generate a normalized dependency graph snapshot.
- Commit it or store it as a build artifact.
- Diff it nightly or weekly and alert on anomalies.
What counts as an anomaly depends on your org, but these are good starters:

- A new dependency that appears across many services in the same day.
- A sudden jump in transitive dependency count.
- A new dependency pulled from a Git URL.
If you’re already producing SBOMs in other languages, treat JavaScript the same way. You don’t need to boil the ocean. You just need one diffable artifact that doesn’t lie.

## The practical recommendation (and my prediction)

If you’re on Bun today, move to `bun.lock` unless you have a very specific reason not to. The moment you care about audits, incident response, or compliance, a binary lockfile is self-inflicted pain.

My prediction: by 2027, “we can’t diff our lockfile” will sound as ridiculous as “we don’t run tests in CI.” Not because everyone suddenly became security saints, but because the first team that gets burned will turn it into policy everywhere else.

Photo by Adhitya Sibikumar on Unsplash.

## FAQ

### What is bun.lockb?

bun.lockb is Bun’s legacy lockfile format that stores the resolved dependency graph in a binary file. It pins direct and transitive dependency versions so installs can be reproducible. The main downside is that it’s hard to review in code review because diffs aren’t human-readable.

### Is bun.lockb a binary lockfile?

Yes. The “b” in bun.lockb refers to Bun’s older binary lockfile format. Bun v1.2 switched the default to a text lockfile named bun.lock to improve reviewability and tooling support.

### Should I commit bun.lock or bun.lockb to git?

You should commit your lockfile to git so installs are reproducible across machines and CI. If you’re on a modern Bun version, prefer committing bun.lock because it’s text and reviewable. Only keep bun.lockb if you’re stuck on an older workflow and have compensating controls like dependency graph snapshots.

### How do I migrate bun.lockb to bun.lock?

Run `bun install --save-text-lockfile --frozen-lockfile --lockfile-only`, then delete bun.lockb. The `--frozen-lockfile` part helps ensure the migration doesn’t change your resolved versions unexpectedly. After migrating, review the new bun.lock diff in a PR like any other dependency change.

### What does --frozen-lockfile do in Bun?

It tells Bun to treat the lockfile as an input, not something it’s allowed to rewrite. In CI, this should fail the build if `package.json` and the lockfile don’t match. It’s one of the simplest ways to prevent silent dependency drift from landing on main.

### How can I generate a lockfile without installing dependencies in Bun?

Use `bun install --lockfile-only`. This updates or generates the lockfile without writing dependencies into `node_modules`. It’s useful in CI for fast validation jobs that avoid running install scripts or mutating the workspace.
