Skip to content

Robustness and input-handling pass across install, shell, TLS/QUIC, HTTP/3, SQL and crypto - #37669

Merged
Jarred-Sumner merged 12 commits into
mainfrom
claude/hardening-sweep-13
Aug 12, 2026
Merged

Robustness and input-handling pass across install, shell, TLS/QUIC, HTTP/3, SQL and crypto#37669
Jarred-Sumner merged 12 commits into
mainfrom
claude/hardening-sweep-13

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

A broad robustness and input-handling pass across install, the shell, TLS/QUIC, HTTP/3 serving, the HTTP client, SQL clients, crypto, and a few runtime corners, ahead of the release. Each item below is a small, self-contained behavior change with a regression test in the existing test file for that module. No API is removed.

Behavior notes, by area

install

  • Symlink entries in GitHub/git tarballs are written after every file and directory entry (both the streaming and buffered extractors share one implementation), so no later entry is ever created through an archive-provided link; the created tree for well-formed archives is unchanged.
  • The streaming extractor keeps its input buffer alive until libarchive has finished with it, including when the tar reader retries past a damaged header block; the end-of-body hand-off between the HTTP thread and the extraction worker happens under the stream lock.
  • workspace: specifiers are only honored in the root and workspace manifests; a workspace: range inside a downloaded package resolves like any other unresolvable range.
  • The on-disk and in-memory manifest caches check the stored package name on load, not just the name hash.
  • --trust <pkg> applies to the packages actually resolved in that package's dependency tree, and the names written to trustedDependencies are the same identity bun install checks on later runs.
  • Git dependencies: a top-level node_modules symlink in the checkout is not carried into node_modules/<pkg>/, and .bun-tag is written without following an existing entry. Committed node_modules directories (bundleDependencies) are untouched.
  • bun install --verbose masks Authorization/Proxy-Authorization header values and credential-bearing URLs in request logs; bunfig password values are redacted in config diagnostics.

shell

  • Cross-device mv opens the source without following a swapped-in link and takes ownership/mode from the opened descriptor.
  • $${value}: an interpolated value directly after a literal $ is always passed as data, never joined into a variable name.
  • Readers and captured writers stay alive for the whole I/O callback that references them, including when a builtin finishes synchronously on a write error (cat > /dev/full, closed pipes).
  • bun run --shell=system on Windows rejects pass-through arguments that cmd.exe would reinterpret.

TLS / node:tls / node:quic

  • A TLS 1.3 session resumed by a client that never presented a certificate reports authorized === false (node:tls, node:https, Bun.listen), same as its original handshake.
  • node:quic: with the default verifyPeer, a client whose server certificate does not validate refuses natively before any queued stream data is sent and before peer streams/datagrams are delivered; session.opened rejects with the same error as before. Servers with verifyClient: true require a client certificate during the handshake.
  • tls.connect({ socket, host: <ip> }) checks the certificate against that IP; Bun.connect over unix:/fd: with tls checks against serverName or localhost.
  • Http2SecureServer connections injected via emit('connection') use the server's full TLS option set (min/max version, ciphers, crl).
  • delete process.env.NODE_TLS_REJECT_UNAUTHORIZED restores the default and later assignments still take effect.

Bun.serve / HTTP/3

  • HTTP/3 requests whose header names or values contain CR, LF or NUL are rejected as malformed, matching the HTTP/1.1 parser; :authority goes through the same host validation as Host.
  • With http3: true, tls.requestCert/rejectUnauthorized are enforced on QUIC connections as they are on TCP.
  • Linux directory routes keep using openat2 resolution after an unrelated EPERM/EINVAL.

fetch / HTTP client

  • Very large file-backed request bodies send the correct Content-Length.

SQL

  • MySQL: result rows larger than one wire packet (16 MiB) are reassembled before decoding.
  • MySQL/Postgres URL and option parsing: ?ssl=, ?ssl-mode=/sslmode= spellings and ssl: "verify-full"-style strings select the corresponding sslmode; tls: { caFile } enables verification like ca does; an explicit option object still wins over the URL.
  • Postgres: an error response for a simple query only detaches that query's own statement.
  • Duplicate column names that differ only in invalid UTF-8 bytes no longer share one property slot.

crypto

  • Bun.SHA*/MD5 update() and Bun.CryptoHasher.hash() coerce every argument before touching hasher state.
  • Ed25519 sign checks that the supplied public half matches the private key.

runtime / bundler / misc

  • Buffer.write/fill/TextEncoder.encodeInto with a string ending in a lone high surrogate size the fast path from the full input.
  • Workers read process.env at runtime like the main thread instead of inlining values at transpile time.
  • module.enableCompileCache() only uses a cache directory owned by the current user with private permissions.
  • Windows bun:ffi cc() creates a fresh private compiler-runtime header directory per process.
  • zlib params() resets stream pointers before deflateParams; BundlerPlugin native filter registration type-checks its arguments; destructuring a macro result with repeated keys and nested macro evaluation keep the AST store consistent.
  • bun publish launches the browser opener by absolute path.

Tests

93 new regression tests across 39 existing test files; each fails on the current release and passes on this branch (three are platform-gated: Windows --shell=system, non-ASAN cc(), and macOS-only extractor assertions run in reduced form elsewhere). cargo clippy --workspace, bun run rust:check-all (10/10 targets), oxlint and prettier are clean.

Related

Larger items from the same pass are in their own PRs: #37590 (security scanner receives every resolution) and follow-ups for install-cache entry identity, off-thread I/O into WebAssembly memory, and TLS common-name fallback parity with Node.js.

@Jarred-Sumner
Jarred-Sumner requested a review from alii as a code owner August 11, 2026 23:23
@robobun

robobun commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator
Updated 6:05 AM PT - Aug 12th, 2026

@Jarred-Sumner, your commit f4f5e74 has some failures in Build #92969 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 37669

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

bun-37669 --bun

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

claude fix the conflicts

@Jarred-Sumner
Jarred-Sumner force-pushed the claude/hardening-sweep-13 branch from cb6f01e to 4ba8325 Compare August 11, 2026 23:38
@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Rebased onto main and resolved the conflicts. The unix-socket redirect change is dropped from this branch since #37451 settled that behavior; everything else is unchanged.

@coderabbitai

coderabbitai Bot commented Aug 11, 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

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: 398ce114-cf02-413c-bbb3-fc08d3c0a6cb

📥 Commits

Reviewing files that changed from the base of the PR and between 6f19a85 and 734d80d.

📒 Files selected for processing (1)
  • test/js/bun/ffi/cc.test.ts

Walkthrough

Changes

This change set updates TLS and QUIC verification, package installation and archive extraction, shell and runtime safety, SQL protocol handling, HTTP behavior, cryptography, and bundler validation.

Possibly related PRs

  • oven-sh/bun#30722: The changes overlap in package manifest caching, TLS handling, and symlink extraction.
  • oven-sh/bun#31559: Both changes update the Node HTTP/2 TLS upgrade path.
  • oven-sh/bun#37649: Both changes update HTTP/3 header validation in packages/bun-usockets/src/quic.c.

Suggested reviewers: alii, robobun

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the PR's broad robustness and input-handling changes across the listed subsystems.
Description check ✅ Passed The description explains the changes, behavior by area, regression tests, and verification commands, despite using different section 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: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/install/PackageManager/PackageManagerLifecycle.rs (1)

482-501: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use an explicit work-list instead of recursion in add_package_to_set.

add_package_to_set recurses once per newly-visited package_id in a dependency chain before the found_existing memoization guard can short-circuit it. The guard stops cycles, but not depth. A deep, non-cyclic transitive dependency chain under a --trust-ed package can drive recursion depth proportional to chain length, risking a stack overflow (a process crash, not a catchable error).

src/install/isolated_install.rs, added in this same PR, treats the same class of dependency-graph walk with an explicit iterative DFS specifically to avoid this risk. Apply the same pattern here: replace the recursive call with an explicit Vec-backed work-list.

🛡️ Proposed iterative rewrite
 fn add_package_to_set(
     set: &mut ArrayHashMap<PackageID, ()>,
     lockfile: &Lockfile,
     package_id: PackageID,
 ) {
-    if handle_oom(set.get_or_put(package_id)).found_existing {
-        return;
-    }
-    let dependencies_slice = lockfile.packages.items_dependencies()[package_id as usize];
-    let begin = dependencies_slice.off;
-    let end = begin.saturating_add(dependencies_slice.len);
-    let mut dep_id = begin;
-    while dep_id < end {
-        let dep_package_id = lockfile.buffers.resolutions[dep_id as usize];
-        if dep_package_id != invalid_package_id {
-            add_package_to_set(set, lockfile, dep_package_id);
-        }
-        dep_id += 1;
-    }
+    let mut stack: Vec<PackageID> = vec![package_id];
+    while let Some(current) = stack.pop() {
+        if handle_oom(set.get_or_put(current)).found_existing {
+            continue;
+        }
+        let dependencies_slice = lockfile.packages.items_dependencies()[current as usize];
+        let begin = dependencies_slice.off;
+        let end = begin.saturating_add(dependencies_slice.len);
+        let mut dep_id = begin;
+        while dep_id < end {
+            let dep_package_id = lockfile.buffers.resolutions[dep_id as usize];
+            if dep_package_id != invalid_package_id {
+                stack.push(dep_package_id);
+            }
+            dep_id += 1;
+        }
+    }
 }
🤖 Prompt for 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.

In `@src/install/PackageManager/PackageManagerLifecycle.rs` around lines 482 -
501, Replace recursive traversal in add_package_to_set with an explicit
Vec-backed work-list implementing iterative DFS. Seed it with package_id, pop
each package, skip processing when
handle_oom(set.get_or_put(...)).found_existing is true, and push each valid
dependency package ID for later processing while preserving the current
dependency bounds and invalid_package_id filtering.
src/libarchive/lib.rs (1)

1738-1738: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep O_NOFOLLOW for regular-file extraction. extract_to_disk accepts an existing caller-supplied path, and Archive.rs passes self.path directly. A pre-existing symlink can therefore redirect openat outside the extraction directory.

🤖 Prompt for 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.

In `@src/libarchive/lib.rs` at line 1738, Update the flag construction in
extract_to_disk for regular-file extraction to include bun_sys::O::NOFOLLOW
alongside WRONLY, CREAT, and TRUNC. Preserve the existing openat flow while
ensuring caller-supplied paths cannot follow pre-existing symlinks.
src/install/TarballStream.rs (1)

1433-1489: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Move symlink-target validation into one shared Unix helper.

symlink_target_stays_inside duplicates is_symlink_target_safe, and the implementations differ for empty targets and dirname resolution. Export one helper from src/libarchive/lib.rs and call it from both extraction paths. The existing #[cfg(unix)] gate already excludes the u16 Windows path.

🤖 Prompt for 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.

In `@src/install/TarballStream.rs` around lines 1433 - 1489, Consolidate symlink
validation by exporting a shared Unix helper from libarchive/lib.rs, based on
the existing is_symlink_target_safe behavior, and remove the duplicated
symlink_target_stays_inside implementation from TarballStream.rs. Update both
extraction paths to call the shared helper, preserving consistent handling of
empty targets and dirname resolution; rely on the existing #[cfg(unix)] gating
for platform differences.
🤖 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/http/lib.rs`:
- Around line 1436-1456: Update print_request to redact sensitive data in curl
verbose output: pass the URL through the existing redaction utility before
printing, and ensure Authorization and Proxy-Authorization header values expose
only their scheme while masking credentials. Preserve normal output for
non-sensitive headers and the existing request formatting.

In `@src/install/PackageManager/PackageManagerEnqueue.rs`:
- Around line 2647-2660: Extract the duplicated root workspace lookup into a
shared helper such as find_root_workspace_resolution, accepting PackageManager
and PackageNameHash and returning the matching PackageID or None. Replace the
loops in the Npm-satisfies block, DistTag fallback block, and Workspace-tag
guard with calls to this helper while preserving each caller’s existing success
and result-handling behavior.

In `@src/install/repository.rs`:
- Line 942: Update the cleanup around dir.delete_file_z so a real node_modules
directory is removed recursively while preserving the existing symlink/file
removal behavior; do not discard failures for the directory case, and keep the
symlink case covered by the current logic.

In `@src/js/internal/sql/shared.ts`:
- Around line 1805-1811: Honor explicit TLS/SSL false values across URL and
option parsing: in the URL handling branch around shared.ts lines 1805-1811, set
sslMode to SSLMode.disable for false or 0; in the alias-resolution logic around
lines 1956-1963, avoid truthiness checks so options.tls or options.ssl preserves
explicit false and disables SSL mode. Add precedence tests covering URL
parameters, option objects, and PGSSLMODE, explicitly distinguishing empty,
zero, false, and unset inputs.

In `@src/jsc/bindings/JSEnvironmentVariableMap.cpp`:
- Around line 800-805: Add a regression case in the worker_threads tests using a
SHARE_ENV worker: set NODE_TLS_REJECT_UNAUTHORIZED to "0", delete the variable,
then issue a request to an invalid-certificate endpoint and assert that it
rejects. Reuse the existing SHARE_ENV worker and TLS request/test helpers where
available.

In `@src/libarchive/lib.rs`:
- Around line 1104-1108: Validate the `dirname`/`symlink.path` relationship
before slicing in the non-empty branch of the surrounding symlink creation flow:
require the directory prefix to be followed by exactly one separator and reject
or safely handle non-normalized paths such as `a//b`. Update the logic around
`dirname_simple` and the `ZStr::from_slice_with_nul` construction so the derived
name can never begin with `/` before calling `bun_sys::symlinkat`.

In `@src/paths/lib.rs`:
- Around line 966-972: Update Path’s is_node_module method to use
case-insensitive matching only on macOS and Windows, while requiring exact
matching against crate::NODE_MODULES_NEEDLE on other targets such as Linux.
Preserve matching against self.name().dir rather than text.

In `@src/runtime/cli/publish_command.rs`:
- Line 1123: Update the browser-opening call in the publish flow to handle the
result from bun_core::spawn_sync_inherit instead of discarding it. When the
opener cannot start or exits unsuccessfully, return or propagate a recoverable
error that includes both open::OPENER and auth_url; preserve the existing
success path when the command completes successfully.

In `@test/bundler/bundler_plugin.test.ts`:
- Around line 1682-1687: Assert complete subprocess results at all three sites:
in test/bundler/bundler_plugin.test.ts:1682-1687, require empty stderr alongside
the existing stdout and exitCode assertions; in
test/bundler/transpiler/macro-test.test.ts:180-182, add the empty-stderr
assertion with the existing last-line and exit-code checks; in
test/bundler/transpiler/transpiler.test.js:4468-4476, assert the expected child
diagnostics, empty stderr as applicable, and successful exit code before opening
out.json, while preserving concurrent stdout, stderr, and exit collection.

In `@test/cli/install/bun-install-streaming-extract.test.ts`:
- Around line 385-386: Replace randomFillSync in the test’s incompressible
payload setup with deterministic 8 MiB data derived from a fixed seed, while
preserving its incompressible characteristics and the existing
streaming-threshold coverage.

In `@test/cli/install/bun-install.test.ts`:
- Around line 5382-5385: Update the `.bun-tag` assertion in the test to require
the deterministic final state produced by the `O_NOFOLLOW` failure cleanup in
`src/install/repository.rs`: assert that the entry is absent, rather than
defaulting a missing entry to a passing non-symlink result. Keep using
`lstatSync` with `throwIfNoEntry: false` and distinguish absence explicitly from
an existing symlink.

In `@test/cli/install/bun-workspaces.test.ts`:
- Around line 1957-1965: Replace the external tar invocation in the affected
workspace test with in-process tarball creation to keep the test hermetic. If
retaining spawn, capture stderr instead of ignoring it and include the captured
message in the assertion alongside the exit code.

In `@test/cli/install/symlink-path-traversal.test.ts`:
- Around line 613-646: Move the exit-code assertions before the filesystem
validation in the test flow containing the `misplaced`, `markerDirs`, and
`pkgDir` checks. After the existing stdout/stderr assertions, add the
house-style `if (exitCode !== 0) { expect(stderr).toBe(""); }` guard immediately
before `expect(exitCode).toBe(0)`, then retain the filesystem checks without the
`console.error` diagnostics.

In `@test/js/bun/http/serve-directory-routes.test.ts`:
- Around line 13-21: The canInjectOpenat2Error setup only validates strace
syntax and discards evidence of injection. Update the test’s strace invocation
and the related route-test setup to write to a temporary trace file, then verify
that the trace contains an injected openat2 EPERM record before proceeding with
route assertions; skip or disable the injection path when that proof is absent.

In `@test/js/bun/shell/bunshell.test.ts`:
- Around line 232-245: Update the affected subprocess tests to capture stderr
and assert it is empty as part of the complete result:
test/js/bun/shell/bunshell.test.ts lines 232-245 for both shell commands,
test/js/bun/util/bun-cryptohasher.test.ts lines 116-131,
test/js/node/worker_threads/worker_threads.test.ts lines 1485-1498,
test/js/web/fetch/fetch.tls.test.ts lines 711-725, and
test/js/web/workers/worker.test.ts lines 190-203. Assert stderr before parsing
stdout where applicable, while preserving concurrent stdout, stderr, and exit
handling and the existing output assertions.

In `@test/js/bun/shell/shell-pipe-read-fault.test.ts`:
- Around line 450-466: Replace the expect.any(String) matchers for stderr and
readerStderr in the shell-pipe fault test at
test/js/bun/shell/shell-pipe-read-fault.test.ts:450-466 with exact empty-string
expectations. Also replace the expect.any(String) stderr matcher in
test/js/bun/shell/yield.test.ts:94-102 with an empty-string expectation,
preserving the remaining assertions.

In `@test/js/node/quic/quic-sni.test.ts`:
- Around line 208-219: Update the subprocess result assertion around stdout and
exitCode to assert a combined object that also includes stderr matched with
expect.any(String). Preserve the exact stdout JSON expectation and exitCode
validation while retaining the drained stderr diagnostics in failure diffs.

In `@test/js/node/quic/quic-stream.test.ts`:
- Around line 219-222: Update the test flow around createBidirectionalStream so
its rejection is caught when the session is closed or closing, including the
expected ERR_INVALID_STATE case. Ensure the rejection is handled before awaiting
Promise.all so the test reaches the combined server/client assertion.

In `@test/js/sql/adapter-env-var-precedence.test.ts`:
- Around line 432-440: Add a separate test case for a URL ending in empty `tls=`
while setting PGSSLMODE=require, and assert the intended SSL mode and
startup-parameter behavior explicitly. Keep the existing explicit false-value
cases unchanged, and use the test’s SQL constructor and environment setup to
document whether empty tls is ignored or disables TLS.

In `@test/js/web/crypto/web-crypto.test.ts`:
- Around line 403-406: Update the structuredClone call in the Ed25519
verification test to clone consistent, the imported key, instead of privateKey.
Keep the subsequent crypto.subtle.sign and verify flow unchanged so the test
exercises the imported key normalization path.

---

Outside diff comments:
In `@src/install/PackageManager/PackageManagerLifecycle.rs`:
- Around line 482-501: Replace recursive traversal in add_package_to_set with an
explicit Vec-backed work-list implementing iterative DFS. Seed it with
package_id, pop each package, skip processing when
handle_oom(set.get_or_put(...)).found_existing is true, and push each valid
dependency package ID for later processing while preserving the current
dependency bounds and invalid_package_id filtering.

In `@src/install/TarballStream.rs`:
- Around line 1433-1489: Consolidate symlink validation by exporting a shared
Unix helper from libarchive/lib.rs, based on the existing is_symlink_target_safe
behavior, and remove the duplicated symlink_target_stays_inside implementation
from TarballStream.rs. Update both extraction paths to call the shared helper,
preserving consistent handling of empty targets and dirname resolution; rely on
the existing #[cfg(unix)] gating for platform differences.

In `@src/libarchive/lib.rs`:
- Line 1738: Update the flag construction in extract_to_disk for regular-file
extraction to include bun_sys::O::NOFOLLOW alongside WRONLY, CREAT, and TRUNC.
Preserve the existing openat flow while ensuring caller-supplied paths cannot
follow pre-existing symlinks.
🪄 Autofix

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: 49d0c738-37e3-46dc-9984-ea4b4f4b1896

📥 Commits

Reviewing files that changed from the base of the PR and between 1d28cc4 and cb6f01e.

📒 Files selected for processing (98)
  • packages/bun-types/sql.d.ts
  • packages/bun-usockets/src/crypto/openssl.c
  • packages/bun-usockets/src/libusockets.h
  • packages/bun-usockets/src/quic.c
  • src/ast/lib.rs
  • src/bun_core/fmt.rs
  • src/bun_core/lib.rs
  • src/bun_core/string/immutable/unicode.rs
  • src/http/AsyncHTTP.rs
  • src/http/InternalState.rs
  • src/http/lib.rs
  • src/install/PackageInstaller.rs
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/install/PackageManager/PackageManagerLifecycle.rs
  • src/install/PackageManager/PackageManagerResolution.rs
  • src/install/PackageManifestMap.rs
  • src/install/TarballStream.rs
  • src/install/isolated_install.rs
  • src/install/isolated_install/Installer.rs
  • src/install/lockfile.rs
  • src/install/lockfile/Package.rs
  • src/install/npm.rs
  • src/install/repository.rs
  • src/install_types/resolver_hooks.rs
  • src/io/PipeReader.rs
  • src/io/lib.rs
  • src/js/builtins/ProcessObjectInternals.ts
  • src/js/internal/sql/shared.ts
  • src/js/node/_http2_upgrade.ts
  • src/js/node/net.ts
  • src/js/node/tls.ts
  • src/js_parser/visit/mod.rs
  • src/jsc/NodeCompileCache.rs
  • src/jsc/bindings/JSBundlerPlugin.cpp
  • src/jsc/bindings/JSEnvironmentVariableMap.cpp
  • src/jsc/bindings/webcrypto/CryptoKeyOKP.cpp
  • src/jsc/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
  • src/jsc/web_worker.rs
  • src/libarchive/lib.rs
  • src/paths/lib.rs
  • src/runtime/cli/publish_command.rs
  • src/runtime/cli/run_command.rs
  • src/runtime/crypto/CryptoHasher.rs
  • src/runtime/ffi/ffi_body.rs
  • src/runtime/node/quic/session.rs
  • src/runtime/node/zlib/NativeZlib.rs
  • src/runtime/server/server_body.rs
  • src/runtime/shell/IOReader.rs
  • src/runtime/shell/builtin/mv.rs
  • src/runtime/shell/shell_body.rs
  • src/runtime/shell/subproc.rs
  • src/runtime/socket/socket_body.rs
  • src/runtime/webcore/Request.rs
  • src/simdutf_sys/simdutf.rs
  • src/sql/mysql/protocol/PacketHeader.rs
  • src/sql/shared/ColumnIdentifier.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • src/sys/lib.rs
  • test/bundler/bundler_plugin.test.ts
  • test/bundler/transpiler/macro-test.test.ts
  • test/bundler/transpiler/transpiler.test.js
  • test/cli/install/bun-install-lifecycle-scripts.test.ts
  • test/cli/install/bun-install-registry.test.ts
  • test/cli/install/bun-install-streaming-extract.test.ts
  • test/cli/install/bun-install.test.ts
  • test/cli/install/bun-publish.test.ts
  • test/cli/install/bun-run.test.ts
  • test/cli/install/bun-workspaces.test.ts
  • test/cli/install/redacted-config-logs.test.ts
  • test/cli/install/symlink-path-traversal.test.ts
  • test/js/bun/ffi/cc.test.ts
  • test/js/bun/http/serve-directory-routes.test.ts
  • test/js/bun/http/serve-http3.test.ts
  • test/js/bun/net/socket.test.ts
  • test/js/bun/shell/bunshell.test.ts
  • test/js/bun/shell/commands/mv.test.ts
  • test/js/bun/shell/shell-pipe-read-fault.test.ts
  • test/js/bun/shell/yield.test.ts
  • test/js/bun/util/bun-cryptohasher.test.ts
  • test/js/node/buffer.test.js
  • test/js/node/http2/node-http2-upgrade.test.mts
  • test/js/node/module/node-module-module.test.js
  • test/js/node/quic/quic-sni.test.ts
  • test/js/node/quic/quic-stream.test.ts
  • test/js/node/tls/node-tls-connect-hostname-verification.test.ts
  • test/js/node/tls/node-tls-server.test.ts
  • test/js/node/worker_threads/worker_threads.test.ts
  • test/js/node/zlib/zlib-handle-bounds-check.test.ts
  • test/js/sql/adapter-env-var-precedence.test.ts
  • test/js/sql/postgres-datarow-overrun.test.ts
  • test/js/sql/postgres-error-then-datarow.test.ts
  • test/js/sql/sql-mysql.test.ts
  • test/js/web/crypto/web-crypto.test.ts
  • test/js/web/fetch/fetch.test.ts
  • test/js/web/fetch/fetch.tls.test.ts
  • test/js/web/fetch/fetch.unix.test.ts
  • test/js/web/workers/worker.test.ts
💤 Files with no reviewable changes (1)
  • src/simdutf_sys/simdutf.rs

Comment thread src/http/lib.rs
Comment thread src/install/PackageManager/PackageManagerEnqueue.rs Outdated
Comment thread src/install/repository.rs
Comment thread src/js/internal/sql/shared.ts
Comment thread src/jsc/bindings/JSEnvironmentVariableMap.cpp
Comment thread test/js/bun/shell/shell-pipe-read-fault.test.ts
Comment thread test/js/node/quic/quic-sni.test.ts Outdated
Comment thread test/js/node/quic/quic-stream.test.ts
Comment thread test/js/sql/adapter-env-var-precedence.test.ts
Comment thread test/js/web/crypto/web-crypto.test.ts Outdated
Comment thread src/http/lib.rs Outdated
Comment thread src/http/lib.rs

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/install/TarballStream.rs (2)

585-599: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Continue when Retry retains the input buffer.

When archive.next returns lib::Result::Retry while archive_holds_reading is true, the current match enters _ and fails extraction. Continue the loop in this state. Return Ok(()) only when archive_holds_reading is false.

Proposed fix
 match block.result {
     lib::Result::Retry if !(*this).archive_holds_reading => return Ok(()),
+    lib::Result::Retry => continue,
     lib::Result::Ok | lib::Result::Warn => {
🤖 Prompt for 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.

In `@src/install/TarballStream.rs` around lines 585 - 599, Update the match
handling around archive.next in TarballStream so lib::Result::Retry with
archive_holds_reading true continues the processing loop instead of reaching the
error branch. Preserve the existing Ok(()) return when archive_holds_reading is
false, while leaving Ok/Warn writes and other errors unchanged.

631-632: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Propagate archive setup failures.

Handle Warn, Failed, and Fatal from read_support_format_tar and read_set_options. Log archive.error_string() and return before opening the archive.

🤖 Prompt for 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.

In `@src/install/TarballStream.rs` around lines 631 - 632, Update the archive
setup in the surrounding installation flow to inspect the results of
read_support_format_tar and read_set_options instead of discarding them. Handle
Warn, Failed, and Fatal by logging archive.error_string() and returning before
the archive is opened, while preserving the existing success path.

Source: Coding guidelines

🤖 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/install/TarballStream.rs`:
- Around line 146-149: Bound deferred symlink accumulation wherever
deferred_symlinks is populated: validate the current symlink count and combined
path/target byte size before copying or pushing the entry. Enforce both a
maximum count and total-byte limit using established extraction-limit
conventions, and return a recoverable extraction error when either limit would
be exceeded.
- Around line 551-553: Update create_deferred_symlinks and its parent-directory
traversal to walk every component from dir_fd using no-follow semantics, rather
than relying on bun_sys::open_dir_at. Reject any symlinked parent component
before performing containment or inode checks, while preserving the existing
deferred symlink creation behavior.

In `@src/runtime/ffi/ffi_body.rs`:
- Around line 2420-2421: The compiler-runtime staging flow must propagate
failures from every CompilerRtSources::NODE_HEADERS write instead of returning
success with a partial directory. Update the NODE_HEADERS loop in the
surrounding staging function to return false immediately when
write_compiler_rt_file fails, and assign COMPILER_RT_DIR and
COMPILER_RT_NODE_DIR only after both source and header loops complete
successfully.

---

Outside diff comments:
In `@src/install/TarballStream.rs`:
- Around line 585-599: Update the match handling around archive.next in
TarballStream so lib::Result::Retry with archive_holds_reading true continues
the processing loop instead of reaching the error branch. Preserve the existing
Ok(()) return when archive_holds_reading is false, while leaving Ok/Warn writes
and other errors unchanged.
- Around line 631-632: Update the archive setup in the surrounding installation
flow to inspect the results of read_support_format_tar and read_set_options
instead of discarding them. Handle Warn, Failed, and Fatal by logging
archive.error_string() and returning before the archive is opened, while
preserving the existing success path.
🪄 Autofix

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: a87baf39-b5ce-40cb-b891-6a4589fa2830

📥 Commits

Reviewing files that changed from the base of the PR and between cb6f01e and 4ba8325.

📒 Files selected for processing (10)
  • src/http/lib.rs
  • src/install/PackageInstaller.rs
  • src/install/PackageManager/PackageManagerLifecycle.rs
  • src/install/TarballStream.rs
  • src/io/lib.rs
  • src/runtime/cli/publish_command.rs
  • src/runtime/ffi/ffi_body.rs
  • src/runtime/server/server_body.rs
  • src/runtime/webcore/Request.rs
  • src/sys/lib.rs

Comment on lines +146 to +149
/// Symlink entries accepted so far; written to disk only after every
/// other entry.
#[cfg(unix)]
created_symlinks: Vec<Vec<u8>>,
deferred_symlinks: Vec<bun_libarchive::DeferredSymlink>,

@coderabbitai coderabbitai Bot Aug 11, 2026

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound deferred symlink storage.

Each accepted symlink copies its path and target until archive EOF. A compressed archive with many symlink headers can cause unbounded heap growth before extraction completes. Enforce a symlink count and total-byte limit before push, and return a recoverable extraction error when either limit is exceeded.

As per coding guidelines, “Validate untrusted input before processing, allocation, or side effects; enforce actual resource limits.”

Also applies to: 862-867

🤖 Prompt for 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.

In `@src/install/TarballStream.rs` around lines 146 - 149, Bound deferred symlink
accumulation wherever deferred_symlinks is populated: validate the current
symlink count and combined path/target byte size before copying or pushing the
entry. Enforce both a maximum count and total-byte limit using established
extraction-limit conventions, and return a recoverable extraction error when
either limit would be exceeded.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Left as is: each deferred entry is at most one normalized path (< PATH_MAX, checked at TarballStream.rs:802) plus one target that is_symlink_target_safe already rejects unless dirname + target fits in a PathBuffer (lib.rs:1032), and both strings are carried verbatim in the tar stream, so the retained bytes are bounded by the decoded archive size — the same order as the per-symlink created_symlinks: Vec<Vec<u8>> this replaces on main. There is no existing entry-count limit in either extractor to align with, and introducing one here would reject valid archives with many links, which is out of scope for this change.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/libarchive/lib.rs --items all --match 'is_symlink_target_safe|DeferredSymlink|create_deferred_symlinks' --view expanded
rg -n -A18 -B12 'fn is_symlink_target_safe|struct DeferredSymlink|impl DeferredSymlink|created_symlinks|deferred_symlinks' \
  src/libarchive/lib.rs src/install/TarballStream.rs
git show HEAD^:src/install/TarballStream.rs 2>/dev/null | rg -n -A14 -B10 'created_symlinks|make_symlink|path_traverses_created_symlink' || true

Length of output: 22436


@Jarred-Sumner, the per-entry bounds are valid. is_symlink_target_safe runs before DeferredSymlink::new copies the normalized path and target.

The aggregate remains unbounded because neither extractor limits the number of accepted symlinks. The buffered extractor has the same Vec<DeferredSymlink> behavior.

I agree that adding a new archive-entry limit here would change existing extraction behavior and is outside this PR. I will not request a change in this PR.

You are interacting with an AI system.

Comment thread src/install/TarballStream.rs
Comment thread src/runtime/ffi/ffi_body.rs

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/js/internal/sql/shared.ts (1)

1956-1962: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore runtime validation for TLS option types.

A JavaScript caller can pass a truthy unsupported value such as tls: 1. Line [1961] copies that value into tls and returns malformed TLS configuration instead of raising $ERR_INVALID_ARG_TYPE. Validate the selected tls or ssl value before assignment. Add a regression test for an invalid truthy value.

The supplied library context identifies this change as removing the previous type validation and invalid-argument exception. As per coding guidelines: “Validate untrusted input before processing, allocation, or side effects.”

🤖 Prompt for 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.

In `@src/js/internal/sql/shared.ts` around lines 1956 - 1962, Restore runtime type
validation in the TLS option handling around tlsOption, rejecting truthy
unsupported tls or ssl values such as numbers with the existing
$ERR_INVALID_ARG_TYPE behavior before assigning to tls. Preserve string
normalization and valid TLS option handling, and add a regression test covering
an invalid truthy value.

Source: Coding guidelines

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

Outside diff comments:
In `@src/js/internal/sql/shared.ts`:
- Around line 1956-1962: Restore runtime type validation in the TLS option
handling around tlsOption, rejecting truthy unsupported tls or ssl values such
as numbers with the existing $ERR_INVALID_ARG_TYPE behavior before assigning to
tls. Preserve string normalization and valid TLS option handling, and add a
regression test covering an invalid truthy value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0978a348-6362-425f-b909-8c70f16a5375

📥 Commits

Reviewing files that changed from the base of the PR and between 4ba8325 and c883f3b.

📒 Files selected for processing (2)
  • src/js/internal/sql/shared.ts
  • test/js/sql/adapter-env-var-precedence.test.ts
💤 Files with no reviewable changes (1)
  • test/js/sql/adapter-env-var-precedence.test.ts

@Jarred-Sumner

Copy link
Copy Markdown
Collaborator Author

Replies to the notes that could not be posted inline:

  • src/libarchive/lib.rs (outside-diff note): Left as is: origin/main never opened file entries with O_NOFOLLOW here — it added NOFOLLOW_ANY (Darwin-only, 0 elsewhere) only after the archive itself had created a symlink, and with symlink creation now deferred until after every file/dir entry is written that condition can no longer be true. Adding an unconditional O_NOFOLLOW would be a behavior change for extracting into an existing tree (a destination file that is already a symlink would fail with ELOOP at src/libarchive/lib.rs:1780 instead of being written), and it only affects the final component, so it is out of scope for this change; the streaming extractor (TarballStream.rs open_output_file) was changed the same way for consistency.
  • src/install/TarballStream.rs (outside-diff note): Done — is_symlink_target_safe is now exported from bun_libarchive and the streaming extractor calls it directly; the local symlink_target_stays_inside copy is removed. The shared helper now also rejects an empty target, which was the one behavioral difference between the two copies.
  • src/install/PackageManager/PackageManagerLifecycle.rs (outside-diff note): Done — add_package_to_set now walks the dependency graph with an explicit Vec work-list instead of recursing; each package id is inserted into the set before being pushed, so the stack holds every reachable package at most once and the invalid_package_id filter and dependency bounds are unchanged.

Comment thread test/js/bun/ffi/cc.test.ts 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/libarchive/lib.rs (1)

1056-1144: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Propagate deferred symlink creation failures.

create_deferred_symlinks skips parent-verification and symlinkat failures, then extraction reports success. This can install a package with required symlinks missing. Return crate::Result<()> from the helper and propagate failures through both EOF paths.

  • src/libarchive/lib.rs#L1056-L1144: return a recoverable error for deferred-symlink verification and creation failures.
  • src/install/TarballStream.rs#L552-L554: propagate the helper error into streaming extraction.
  • src/libarchive/lib.rs#L1962-L1963: propagate the helper error into buffered extraction.

As per coding guidelines, “Never swallow failures or signal success after failure; propagate I/O, syscall, cleanup, and requested-operation errors explicitly.”

🤖 Prompt for 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.

In `@src/libarchive/lib.rs` around lines 1056 - 1144, Change
create_deferred_symlinks in src/libarchive/lib.rs:1056-1144 to return
crate::Result<()> and propagate parent-verification and symlinkat failures as
recoverable errors instead of logging and continuing; update
src/install/TarballStream.rs:552-554 and src/libarchive/lib.rs:1962-1963 to use
? when invoking it so both streaming and buffered extraction report
deferred-symlink failures.

Source: Coding guidelines

src/install/TarballStream.rs (1)

863-868: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound deferred symlink storage.

Each symlink copies archive-controlled path and target bytes until extraction reaches EOF. A tarball with many symlink entries can exhaust memory in both extraction paths. Enforce a shared maximum entry count and byte budget before DeferredSymlink::new. Return an extraction error when the next entry exceeds either limit.

  • src/install/TarballStream.rs#L863-L868: enforce the limits before adding a streaming entry.
  • src/libarchive/lib.rs#L1716-L1717: enforce the same limits before adding a buffered entry.

As per coding guidelines, “Validate untrusted input before processing, allocation, or side effects; enforce actual resource limits.”

🤖 Prompt for 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.

In `@src/install/TarballStream.rs` around lines 863 - 868, The deferred symlink
paths lack shared resource limits, allowing untrusted archives to exhaust
memory. In src/install/TarballStream.rs:863-868, validate the entry count and
cumulative path/target byte budget before DeferredSymlink::new and return an
extraction error when either limit would be exceeded; apply the same
pre-allocation checks to the buffered-entry path in
src/libarchive/lib.rs:1716-1717, using shared limits and accounting so both
extraction paths enforce the actual bounds.

Source: Coding guidelines

♻️ Duplicate comments (1)
src/js/internal/sql/shared.ts (1)

1809-1811: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep an explicit false value authoritative.

For ?tls=true&ssl=false, Line 1809 changes only sslMode. tls remains true, so Line 2065 changes the mode back to require.

For { tls: false, ssl: true }, Line 1958 selects ssl: true. The false branch does not run.

Clear tls when a URL value disables TLS. Resolve the aliases by undefined status, not truthiness. Add regression cases for both combinations.

Proposed fix
 } else if (value === "false" || value === "0") {
+  tls = undefined;
   sslMode = SSLMode.disable;
 }

-const tlsOption = options.tls || options.ssl;
+const tlsOption = options.tls === undefined ? options.ssl : options.tls;
 ...
-} else if (!tlsOption && (options.tls === false || options.ssl === false)) {
+} else if (tlsOption === false) {

As per coding guidelines: “Deliberately enumerate input spaces, distinguishing empty, zero, and unset.”

Also applies to: 1962-1964

🤖 Prompt for 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.

In `@src/js/internal/sql/shared.ts` around lines 1809 - 1811, Update the URL
parsing branch for sslMode so an explicit SSL-disabled value also clears tls,
preventing later precedence logic from restoring require mode. In the
option-resolution logic around the tls/ssl aliases, distinguish unset values
using undefined checks rather than truthiness so explicit false remains
authoritative while ssl: true still resolves correctly. Add regression cases
covering ?tls=true&ssl=false and { tls: false, ssl: true }.

Source: Coding guidelines

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

Outside diff comments:
In `@src/install/TarballStream.rs`:
- Around line 863-868: The deferred symlink paths lack shared resource limits,
allowing untrusted archives to exhaust memory. In
src/install/TarballStream.rs:863-868, validate the entry count and cumulative
path/target byte budget before DeferredSymlink::new and return an extraction
error when either limit would be exceeded; apply the same pre-allocation checks
to the buffered-entry path in src/libarchive/lib.rs:1716-1717, using shared
limits and accounting so both extraction paths enforce the actual bounds.

In `@src/libarchive/lib.rs`:
- Around line 1056-1144: Change create_deferred_symlinks in
src/libarchive/lib.rs:1056-1144 to return crate::Result<()> and propagate
parent-verification and symlinkat failures as recoverable errors instead of
logging and continuing; update src/install/TarballStream.rs:552-554 and
src/libarchive/lib.rs:1962-1963 to use ? when invoking it so both streaming and
buffered extraction report deferred-symlink failures.

---

Duplicate comments:
In `@src/js/internal/sql/shared.ts`:
- Around line 1809-1811: Update the URL parsing branch for sslMode so an
explicit SSL-disabled value also clears tls, preventing later precedence logic
from restoring require mode. In the option-resolution logic around the tls/ssl
aliases, distinguish unset values using undefined checks rather than truthiness
so explicit false remains authoritative while ssl: true still resolves
correctly. Add regression cases covering ?tls=true&ssl=false and { tls: false,
ssl: true }.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7ed3f5a5-947f-4975-80ec-520ed9d074f9

📥 Commits

Reviewing files that changed from the base of the PR and between c883f3b and 6f19a85.

📒 Files selected for processing (25)
  • src/http/lib.rs
  • src/install/PackageManager/PackageManagerEnqueue.rs
  • src/install/PackageManager/PackageManagerLifecycle.rs
  • src/install/TarballStream.rs
  • src/install/repository.rs
  • src/js/internal/sql/shared.ts
  • src/libarchive/lib.rs
  • src/runtime/ffi/ffi_body.rs
  • test/bundler/bundler_plugin.test.ts
  • test/bundler/transpiler/macro-test.test.ts
  • test/bundler/transpiler/transpiler.test.js
  • test/cli/install/bun-install-streaming-extract.test.ts
  • test/cli/install/bun-install.test.ts
  • test/cli/install/bun-workspaces.test.ts
  • test/cli/install/symlink-path-traversal.test.ts
  • test/js/bun/http/serve-directory-routes.test.ts
  • test/js/bun/shell/bunshell.test.ts
  • test/js/bun/shell/shell-pipe-read-fault.test.ts
  • test/js/bun/shell/yield.test.ts
  • test/js/bun/util/bun-cryptohasher.test.ts
  • test/js/node/quic/quic-sni.test.ts
  • test/js/node/worker_threads/worker_threads.test.ts
  • test/js/sql/adapter-env-var-precedence.test.ts
  • test/js/web/crypto/web-crypto.test.ts
  • test/js/web/fetch/fetch.tls.test.ts

Checkpoint of the batch pass; tests follow in a later commit.

No-Verification-Needed: unpushed WIP checkpoint; verification runs before push
… across the read loop

No-Verification-Needed: unpushed WIP checkpoint; verification runs before push
…i changes

No-Verification-Needed: unpushed WIP checkpoint; verification runs before push
No-Verification-Needed: unpushed WIP checkpoint; verification runs before push
No-Verification-Needed: reverts one hunk to origin/main behavior
No-Verification-Needed: user asked to skip /verify; code review in progress on the branch
…fi header staging, test assertions

No-Verification-Needed: user asked to skip /verify; review round on an open PR
No-Verification-Needed: test-only gate restore
@Jarred-Sumner
Jarred-Sumner force-pushed the claude/hardening-sweep-13 branch from 734d80d to 80834ec Compare August 12, 2026 01:25
Comment thread src/runtime/shell/builtin/mv.rs
No-Verification-Needed: review follow-up on an open PR; behavior identical (same inode)
Comment thread src/js/internal/sql/shared.ts
No-Verification-Needed: review follow-up on an open PR; covered by the precedence tests

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

I've reviewed the latest revision — all four earlier inline findings (verbose-fetch header quoting, the dropped isASAN gate on the cc() tests, the mv directory-branch stat shadowing, and ?ssl=true serverName handling) have been addressed and this pass found nothing new. Given the breadth of security-sensitive changes here — TLS 1.3 resumed-session peer verification in openssl.c, QUIC client-cert enforcement, deferred-symlink tarball extraction, Ed25519 public-half validation, compile-cache directory ownership, and install trust-propagation — a human sign-off is still warranted.

Checked in this pass: the ?ssl=true fix now sets sslMode before the serverName block and the test asserts { serverName: 'h' }; the mv directory arm shadows st/mode from sst; describe.skipIf(isASAN) is restored on the cc() header-directory suite; the us_internal_verify_peer_certificate change correctly narrows the exemption to PSK ciphers only.

Extended reasoning...

Overview

This PR is a broad robustness/hardening pass touching 96 files across install (tarball symlink deferral, manifest-cache name checks, --trust scope, git-dep .bun-tag/node_modules handling, workspace-specifier confinement), the shell (cross-device mv TOCTOU, $${var} interpolation, IOReader keepalive), TLS/QUIC (TLS 1.3 resumed-session authorized, node:quic verifyPeer enforcement, HTTP/3 requestCert, tls.connect({socket, host}) identity checks), Bun.serve HTTP/3 (header validation, :authority host filtering), the HTTP client (Content-Length buffer sizing, credential redaction in verbose logs), SQL (MySQL split-packet reassembly, ssl/sslmode option parsing, Postgres statement-map identity check, invalid-UTF-8 column names), crypto (Ed25519 public-half check, CryptoHasher argument coercion order), and assorted runtime corners (worker env inlining, NODE_TLS_REJECT_UNAUTHORIZED delete, compile-cache dir ownership, Windows ffi header dir naming, zlib params buffer reset, BundlerPlugin filter type checks, macro AST-store reset nesting).

Security risks

Nearly every subsystem touched has a security angle: us_internal_verify_peer_certificate drops the TLS 1.3 session-reused exemption so a resumed session without a client cert no longer reports authorized === true; QUIC now enforces rejectUnauthorized at connection accept and validates HTTP/3 field names/values against RFC 9113 token rules; tarball extraction defers all symlinks until after file/dir entries so no later entry is created through an archive-provided link; module.enableCompileCache() refuses group/world-writable or non-owned directories with O_NOFOLLOW; Ed25519 JWK import verifies the trailing public half matches the seed-derived key via constant-time compare; Windows --shell=system rejects cmd.exe metachars in passthrough args; bun publish no longer looks up xdg-open in the package cwd. These are hardenings, but each is a behavior change on a code path where the wrong direction is a vulnerability.

Level of scrutiny

High. This is exactly the class of PR the approval guidelines say not to auto-approve: large, cross-cutting, and concentrated in TLS/crypto/permissions/extraction paths. Each individual change is small and well-tested (94 new regression tests, each in the existing test file for its module), but the aggregate surface is too wide for an automated sign-off.

Other factors

Four prior inline findings from earlier passes were all addressed in follow-up commits (184c03d3, 2b8cd791, 80834ece, and the earlier review-feedback commit). CodeRabbit's threads are all resolved or withdrawn. The remaining unresolved inline comment (?ssl=true serverName) is now fixed in the diff — the 'true' | '1' branch bumps sslMode to prefer and the test asserts the full { serverName: 'h' } shape. No new issues surfaced in this run.

Comment thread src/paths/lib.rs Outdated
/// parsed dir component (`name.dir`, NOT `text`).
pub fn is_node_module(&self) -> bool {
crate::strings::contains(self.name().dir, crate::NODE_MODULES_NEEDLE)
crate::strings::contains_case_insensitive_ascii(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Let's revert this one. contains_case_insensitive_ascii is a windowed strncasecmp per byte offset (~180ns on a typical path vs ~56ns for the memmem it replaces), and is_node_module() runs per parsed file (ParseTask, HMR lowering, json) and per package resolution in the resolver. Nobody runs into a directory spelled NODE_MODULES — not worth a slower primitive on that path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reverted — is_node_module() is back to the plain contains, and the test that exercised the alternate-casing path is removed.

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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants