Skip to content

crypto: cap GCM IV length at 128 bytes in createCipheriv - #34092

Merged
Jarred-Sumner merged 1 commit into
mainfrom
farm/64fae715/gcm-iv-length-cap
Aug 13, 2026
Merged

Jarred-Sumner merged 1 commit into
mainfrom
farm/64fae715/gcm-iv-length-cap

Conversation

@robobun

@robobun robobun commented Jul 13, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

crypto.createCipheriv and createDecipheriv accepted GCM IVs of any length (tested up to 64 MiB). Node.js rejects GCM IVs longer than 128 bytes with ERR_CRYPTO_INVALID_IV because OpenSSL 3's GCM provider enforces GCM_IV_MAX_SIZE = 1024 / 8. BoringSSL has no such cap, so EVP_CTRL_AEAD_SET_IVLEN silently accepted the oversized IV.

Two consequences:

  • An attacker-controlled IV length is absorbed as GHASH work inside a single constructor call (a 64 MiB IV is ~4M block multiplications) instead of being rejected at the API boundary.
  • Ciphertext produced with an IV >128 bytes cannot be deciphered by Node.js (createDecipheriv throws).

Reproduction

import crypto from "node:crypto";
const key = Buffer.alloc(16);
for (const n of [12, 128, 129, 4096, 1 << 20]) {
  try {
    crypto.createCipheriv("aes-128-gcm", key, Buffer.alloc(n, 7)).final();
    console.log(`iv len=${n} ACCEPTED`);
  } catch (e) {
    console.log(`iv len=${n} rejected: ${e.code}`);
  }
}
iv length Node v26.3.0 Bun before Bun after
12 accepted accepted accepted
128 accepted accepted accepted
129 ERR_CRYPTO_INVALID_IV accepted ERR_CRYPTO_INVALID_IV
4096 ERR_CRYPTO_INVALID_IV accepted ERR_CRYPTO_INVALID_IV
1048576 ERR_CRYPTO_INVALID_IV accepted ERR_CRYPTO_INVALID_IV

Auth tags for the 12- and 128-byte rows are byte-identical across Node and Bun, isolating the divergence to the missing upper bound.

How did you verify your code works?

  • bun bd test test/js/bun/crypto/cipheriv-decipheriv.test.ts (new test fails on main, passes with fix)
  • bun bd test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js
  • bun bd test/js/node/test/parallel/test-crypto-authenticated.js
  • bun bd test/js/node/test/parallel/test-crypto-authenticated-stream.js

[stamp-90s] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/crypto/cipheriv-decipheriv.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (944509692)

test/js/bun/crypto/cipheriv-decipheriv.test.ts:
(pass) should encrypt & decrypt using update & final interface [31.54ms]
(pass) should encrypt & decrypt using streaming interface [321.97ms]
(pass) should fail when cipher is not defined [6.95ms]
(pass) should fail when key is not defined [5.32ms]
(pass) should fail when iv is not defined [4.73ms]
(pass) should fail when key length is invalid [12.31ms]
(pass) should fail when iv length is invalid [12.61ms]
(pass) only zero-sized iv or null should be accepted in ECB mode [16.14ms]
(pass) should allow only valid iv lengths in GCM mode [20.83ms]
124 |   // Node.js (OpenSSL 3) caps GCM IV length at 1024 bits / 128 bytes.
125 |   const invalidIV = { code: "ERR_CRYP
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (1498d7b77)

test/js/bun/crypto/cipheriv-decipheriv.test.ts:
(pass) should encrypt & decrypt using update & final interface [0.62ms]
(pass) should encrypt & decrypt using streaming interface [4.72ms]
(pass) should fail when cipher is not defined [0.12ms]
(pass) should fail when key is not defined [0.05ms]
(pass) should fail when iv is not defined [0.05ms]
(pass) should fail when key length is invalid [0.12ms]
(pass) should fail when iv length is invalid [0.10ms]
(pass) only zero-sized iv or null should be accepted in ECB mode [0.18ms]
(pass) should allow only valid iv lengths in GCM mode [0.25ms]
124 |   // Node.js (OpenSSL 3) caps GCM IV length at 1024 bits / 128 bytes.
125 |   const invalidIV = { code: "ERR_CRYPTO_INVALID_IV" };
126 |   for (const algo of ["aes-128-gcm", "aes-192-gcm", "aes-256-gcm"] as const) {
127 |     const key = randomBytes(algo === "aes-128-gcm" ? 16 : algo === "aes-192-gcm" ? 24 : 32);
128 |     expect(() => createCipheriv(algo, key, Buffer.alloc(128))).not.toThrow();
129 |     expect(() => createCipheriv(algo, key, Buffer.alloc(129))).toThrow(expect.objectContaining(invalidIV));
                                    
... (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/js/bun/crypto/cipheriv-decipheriv.test.ts
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (944509692)

test/js/bun/crypto/cipheriv-decipheriv.test.ts:
(pass) should encrypt & decrypt using update & final interface [31.36ms]
(pass) should encrypt & decrypt using streaming interface [308.79ms]
(pass) should fail when cipher is not defined [6.76ms]
(pass) should fail when key is not defined [5.28ms]
(pass) should fail when iv is not defined [4.72ms]
(pass) should fail when key length is invalid [12.30ms]
(pass) should fail when iv length is invalid [11.37ms]
(pass) only zero-sized iv or null should be accepted in ECB mode [15.79ms]
(pass) should allow only valid iv lengths in GCM mode [21.75ms]
(pass) should reject GCM IVs longer than 128 bytes [29.28ms]
(pass) should encrypt & decrypt well-known values [37.73ms
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 854ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/7] cxx obj/unified/UnifiedSource-src_jsc_bindings_node_crypto-1.cpp.o
[2/7] cxx obj/unified/UnifiedSource-src_jsc_bindings_node_crypto-0.cpp.o
[3/7] gen cpp.rs (cppbind)
[3/7] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

info: checking for self-update (current version: 1.29.0)
�[1m�[92m    Blocking�[0m waiting for file lock on build directory
�[1m�[92m    Finished�[0m `release` profile [opt
... (truncated)
diff hotspot
src/jsc/bindings/node/crypto/JSCipherConstructor.cpp        | 10 ++++++++++
 test/js/bun/crypto/cipheriv-decipheriv.test.ts              | 13 +++++++++++++
 .../node/test/parallel/test-crypto-cipheriv-decipheriv.js   |  4 +++-
 3 files changed, 26 insertions(+), 1 deletion(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                                      reads  edits  tests
src/jsc/bindings/node/crypto/JSCipherConstructor.cpp          1      1      0
test/js/bun/crypto/cipheriv-decipheriv.test.ts                1      1      0
…s/node/test/parallel/test-crypto-cipheriv-decipheriv.js      1      1      0

Node.js (via OpenSSL 3's GCM provider) rejects GCM IVs longer than
1024 bits with ERR_CRYPTO_INVALID_IV. BoringSSL's EVP_CTRL_AEAD_SET_IVLEN
has no such cap, so Bun accepted arbitrarily long IVs, turning an
attacker-controlled IV length into unbounded GHASH work inside the
constructor and producing ciphertext Node cannot decipher.

Enforce the same 128-byte cap at the constructor for GCM ciphers.
@robobun

robobun commented Jul 13, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 5:32 PM PT - Jul 13th, 2026

✅ @robobun, your commit 94450969278d6af060a6a66dd7ca1cfa279c12ab passed in Build #72557! 🎉


🧪   To try this PR locally:

bunx bun-pr 34092

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

bun-34092 --bun

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3d8ed216-29d5-4a4e-8fba-04a9066448b6

📥 Commits

Reviewing files that changed from the base of the PR and between eb0d38b and 9445096.

📒 Files selected for processing (3)
  • src/jsc/bindings/node/crypto/JSCipherConstructor.cpp
  • test/js/bun/crypto/cipheriv-decipheriv.test.ts
  • test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js

Walkthrough

Changes

GCM cipher construction now rejects IVs longer than 128 bytes. Tests cover the 128-byte boundary, oversized IVs across AES-GCM variants, and the corresponding Node compatibility limit.

GCM IV Validation

Layer / File(s) Summary
Runtime GCM IV check
src/jsc/bindings/node/crypto/JSCipherConstructor.cpp
constructCipher throws CRYPTO_INVALID_IV for GCM IVs exceeding 128 bytes.
IV boundary test coverage
test/js/bun/crypto/cipheriv-decipheriv.test.ts, test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js
Tests accept 128-byte IVs, reject 129-byte and 4096-byte IVs across AES-GCM variants, and update the Node compatibility boundary.

Suggested reviewers: jarred-sumner, cirospaciari

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: capping GCM IV length at 128 bytes for cipher creation.
Description check ✅ Passed The description includes both required sections and covers behavior changes, motivation, reproduction, and verification.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

Beyond the inline nit, I also checked that ivView cannot be null at the new GCM check — GCM's getIvLength() is non-zero, so the earlier !ivView && expectedIvLen != 0 guard has already returned ERR_CRYPTO_INVALID_IV; the ASSERT(ivView) is safe and mirrors the ChaCha20-Poly1305 block directly above.

Extended reasoning...

The added block follows the exact shape of the adjacent isChaCha20Poly1305() check, and the only non-obvious question is whether ivView can be null there. It cannot: for GCM ciphers cipher.getIvLength() returns 12, so a null ivView is rejected by the !ivView && expectedIvLen != 0 branch a few lines earlier. The change strictly tightens validation to match Node/OpenSSL 3, so I'm not approving only because it's in the crypto path — the code itself looks correct.

Comment thread src/jsc/bindings/node/crypto/JSCipherConstructor.cpp
@Jarred-Sumner
Jarred-Sumner merged commit 62b088b into main Aug 13, 2026
80 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/64fae715/gcm-iv-length-cap branch August 13, 2026 02:56
alii pushed a commit that referenced this pull request Aug 17, 2026
#39445)

Stacked on #36463 (the base branch is that PR's branch, so the diff here
is only the additions). Merging this into #36463 adds the behavior
changes listed below; #36463 itself now covers the #38333 install batch,
the optional-peer correction, and the TOML / `bun init` fixes, so this
PR no longer touches those.

### Problem
- These 1.3 to 1.4 behavior changes are not in the guide at `701b3e2a0`:
- MySQL: the first `caching_sha2_password` connection over plain TCP is
refused unless `allowPublicKeyRetrieval: true` (#31129; 1.3.14 requested
the key automatically, `MySQLConnection.zig` in the 1.3.14 tag). SQL
`tls` / `ssl` options now require TLS instead of falling back to
plaintext, and `?ssl=` / `?ssl-mode=` are read (`shared.ts` 1.3.14 only
read `?sslmode=`; #37669).
- Install: `~/.npmrc` fallback when `XDG_CONFIG_HOME` is set (#36289),
credentials in `--registry` / env / bunfig object URLs are sent and
outrank same-host `.npmrc` tokens (#38796, #38824), `bun outdated` exits
1 on fetch failures (#38809), new `dedupe` / `up` commands shadow
scripts of those names and `bun feedback` is removed (#38333, #38444),
`workspace:` ranges inside registry packages (#37669), isolated store
entry names (#39014).
- Runtime: `module.enableCompileCache()` / `NODE_COMPILE_CACHE`
implemented (#34660), `require()` / `import` not-found messages
(#34660), `AbortError` message without the period (#39277; 1.3.14's
`BunCommonStrings.h` has the period), GCM IV length (#34092),
`mkdtemp("")` (#34908), vm options (#38381), `server.reload` (#38697),
ICU 75/73 to 78 (#38013), Compression stream chunking (#38695),
`Bun.SQL` sqlite bindings (#35950).
- Bundler: `splitting` with `cjs` / `iife` is an error (#32685),
block-scoped `enum` lowers to `let` (#34249), exports emitted ascending
instead of descending (#35957; `doStep5.zig` in 1.3.14 used `sortDesc`),
minified `$` (#35668).
- The TOML integer bullet did not say what the limit or the fix is.

### Fix
- Adds a MySQL public key section (plus a summary table row), a TLS note
under the `PGSSLMODE` section, an `.npmrc` / credentials addendum to the
`bunfig.toml` section, a `module.enableCompileCache()` section, and the
rest as bullets in the existing lists.
- `docs/pm/overrides.mdx`: one-line change adding a pointer to this
guide in the existing `lockfileVersion` 3 limitation. (The base branch
briefly had a duplicate "Nested overrides" section; it removed that
itself in `8257d01acb`, and this PR was rebased over it.)
- Verification: each runtime claim was run against
`1.4.0-canary.1+8326d1bd3` (22 commits behind main; contains every
change referenced), and each install or bundler claim was checked
against the source on main, with the 1.3 side taken from the
`bun-v1.3.14` tag where the PR body did not state it. The
`/runtime/sql#mysql` and `/upgrade-to-1.4` links resolve. `prettier
--check` passes.

### Not included on purpose
- Lifecycle scripts no longer receiving `npm_package_name` /
`npm_package_version` / `npm_package_json` / `npm_config_local_prefix`
during `bun install`, and transitive `"*"` ranges no longer
deduplicating onto the root's version: regressions with open fixes
(#36690, #38110, #38770). They need either the fixes or a guide line
before release.
- Postgres `sslmode=prefer` / `allow` (including `PGSSLMODE=prefer`,
which 1.4 newly reads) hangs until the connection timeout against a
server without SSL because nothing sends the startup message after the
`N` reply. Same code in 1.3.14; filed as a bug instead of documented.

<details>
<summary>Commands used to verify the runtime claims</summary>

```
timers/promises setTimeout with an aborted signal             # "The operation was aborted"
bun req.cjs                                                   # Cannot find module ... Require stack:
bun b.mjs (import() of a missing package / relative file)      # Cannot find package 'x' imported from /path, ERR_MODULE_NOT_FOUND
bun a_static.mjs (unhandled static import)                    # printed line still: Cannot find package 'x' from '/path'
process.versions.icu                                          # 78.3
createCipheriv("aes-128-gcm", key, Buffer.alloc(129))         # ERR_CRYPTO_INVALID_IV
DecompressionStream of a 1 MiB gzip member                    # 16 chunks of 65536 bytes
new SQL("sqlite://:memory:") with ${[1,2]} / ${new Date()}    # Binding expected ...
fs.mkdtempSync("")                                            # EINVAL
vm.runInThisContext("1", [])                                  # ERR_INVALID_ARG_TYPE
NODE_COMPILE_CACHE=/tmp/cc bun cc.cjs                         # creates /tmp/cc/v1.4.0-x86_64-<sha>-<uid>
NODE_DISABLE_COMPILE_CACHE=1 + enableCompileCache()           # status 3 (DISABLED)
bun dedupe / bun up with package.json scripts of those names  # built-in command runs
bun feedback                                                  # Script not found "feedback"
Bun.build({ splitting: true, format: "cjs" })                 # Code splitting is currently only supported ...
bun build of a function-scoped enum and import * as ns         # let Color; exports a, m, z
new SQL({ url: "postgres://...", tls: true }) on a non-TLS server  # ERR_POSTGRES_TLS_NOT_AVAILABLE
Bun.TOML.parse("a = 9007199254740993")                        # Integer cannot be losslessly represented ...
```

</details>

<details>
<summary>Previous revision</summary>

The first revision of this PR (`3c5611454a`) also rewrote the package
manager section for #38333 / #38853 (nested overrides and
`lockfileVersion: 3`, the optional-peer correction, `bun update`,
`bunfig.toml` over `.npmrc`, `--filter`) and fixed the TOML date and
`bun init` lines. #36463 picked those up in its own commits the same
day, so this PR was rebased onto its new head and reduced to the items
above.

</details>

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 0 · docs-only change; test-proof not
applicable

<!-- robobun:evidence:end -->
robobun added a commit that referenced this pull request Aug 21, 2026
#39445)

Stacked on #36463 (the base branch is that PR's branch, so the diff here
is only the additions). Merging this into #36463 adds the behavior
changes listed below; #36463 itself now covers the #38333 install batch,
the optional-peer correction, and the TOML / `bun init` fixes, so this
PR no longer touches those.

### Problem
- These 1.3 to 1.4 behavior changes are not in the guide at `701b3e2a0`:
- MySQL: the first `caching_sha2_password` connection over plain TCP is
refused unless `allowPublicKeyRetrieval: true` (#31129; 1.3.14 requested
the key automatically, `MySQLConnection.zig` in the 1.3.14 tag). SQL
`tls` / `ssl` options now require TLS instead of falling back to
plaintext, and `?ssl=` / `?ssl-mode=` are read (`shared.ts` 1.3.14 only
read `?sslmode=`; #37669).
- Install: `~/.npmrc` fallback when `XDG_CONFIG_HOME` is set (#36289),
credentials in `--registry` / env / bunfig object URLs are sent and
outrank same-host `.npmrc` tokens (#38796, #38824), `bun outdated` exits
1 on fetch failures (#38809), new `dedupe` / `up` commands shadow
scripts of those names and `bun feedback` is removed (#38333, #38444),
`workspace:` ranges inside registry packages (#37669), isolated store
entry names (#39014).
- Runtime: `module.enableCompileCache()` / `NODE_COMPILE_CACHE`
implemented (#34660), `require()` / `import` not-found messages
(#34660), `AbortError` message without the period (#39277; 1.3.14's
`BunCommonStrings.h` has the period), GCM IV length (#34092),
`mkdtemp("")` (#34908), vm options (#38381), `server.reload` (#38697),
ICU 75/73 to 78 (#38013), Compression stream chunking (#38695),
`Bun.SQL` sqlite bindings (#35950).
- Bundler: `splitting` with `cjs` / `iife` is an error (#32685),
block-scoped `enum` lowers to `let` (#34249), exports emitted ascending
instead of descending (#35957; `doStep5.zig` in 1.3.14 used `sortDesc`),
minified `$` (#35668).
- The TOML integer bullet did not say what the limit or the fix is.

### Fix
- Adds a MySQL public key section (plus a summary table row), a TLS note
under the `PGSSLMODE` section, an `.npmrc` / credentials addendum to the
`bunfig.toml` section, a `module.enableCompileCache()` section, and the
rest as bullets in the existing lists.
- `docs/pm/overrides.mdx`: one-line change adding a pointer to this
guide in the existing `lockfileVersion` 3 limitation. (The base branch
briefly had a duplicate "Nested overrides" section; it removed that
itself in `8257d01acb`, and this PR was rebased over it.)
- Verification: each runtime claim was run against
`1.4.0-canary.1+8326d1bd3` (22 commits behind main; contains every
change referenced), and each install or bundler claim was checked
against the source on main, with the 1.3 side taken from the
`bun-v1.3.14` tag where the PR body did not state it. The
`/runtime/sql#mysql` and `/upgrade-to-1.4` links resolve. `prettier
--check` passes.

### Not included on purpose
- Lifecycle scripts no longer receiving `npm_package_name` /
`npm_package_version` / `npm_package_json` / `npm_config_local_prefix`
during `bun install`, and transitive `"*"` ranges no longer
deduplicating onto the root's version: regressions with open fixes
(#36690, #38110, #38770). They need either the fixes or a guide line
before release.
- Postgres `sslmode=prefer` / `allow` (including `PGSSLMODE=prefer`,
which 1.4 newly reads) hangs until the connection timeout against a
server without SSL because nothing sends the startup message after the
`N` reply. Same code in 1.3.14; filed as a bug instead of documented.

<details>
<summary>Commands used to verify the runtime claims</summary>

```
timers/promises setTimeout with an aborted signal             # "The operation was aborted"
bun req.cjs                                                   # Cannot find module ... Require stack:
bun b.mjs (import() of a missing package / relative file)      # Cannot find package 'x' imported from /path, ERR_MODULE_NOT_FOUND
bun a_static.mjs (unhandled static import)                    # printed line still: Cannot find package 'x' from '/path'
process.versions.icu                                          # 78.3
createCipheriv("aes-128-gcm", key, Buffer.alloc(129))         # ERR_CRYPTO_INVALID_IV
DecompressionStream of a 1 MiB gzip member                    # 16 chunks of 65536 bytes
new SQL("sqlite://:memory:") with ${[1,2]} / ${new Date()}    # Binding expected ...
fs.mkdtempSync("")                                            # EINVAL
vm.runInThisContext("1", [])                                  # ERR_INVALID_ARG_TYPE
NODE_COMPILE_CACHE=/tmp/cc bun cc.cjs                         # creates /tmp/cc/v1.4.0-x86_64-<sha>-<uid>
NODE_DISABLE_COMPILE_CACHE=1 + enableCompileCache()           # status 3 (DISABLED)
bun dedupe / bun up with package.json scripts of those names  # built-in command runs
bun feedback                                                  # Script not found "feedback"
Bun.build({ splitting: true, format: "cjs" })                 # Code splitting is currently only supported ...
bun build of a function-scoped enum and import * as ns         # let Color; exports a, m, z
new SQL({ url: "postgres://...", tls: true }) on a non-TLS server  # ERR_POSTGRES_TLS_NOT_AVAILABLE
Bun.TOML.parse("a = 9007199254740993")                        # Integer cannot be losslessly represented ...
```

</details>

<details>
<summary>Previous revision</summary>

The first revision of this PR (`3c5611454a`) also rewrote the package
manager section for #38333 / #38853 (nested overrides and
`lockfileVersion: 3`, the optional-peer correction, `bun update`,
`bunfig.toml` over `.npmrc`, `--filter`) and fixed the TOML date and
`bun init` lines. #36463 picked those up in its own commits the same
day, so this PR was rebased onto its new head and reduced to the items
above.

</details>

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 0 · docs-only change; test-proof not
applicable

<!-- robobun:evidence:end -->
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