Skip to content

Hardening: input validation and bounds tightening across 26 subsystems#31129

Merged
Jarred-Sumner merged 61 commits into
mainfrom
claude/hardening-sweep
May 21, 2026
Merged

Hardening: input validation and bounds tightening across 26 subsystems#31129
Jarred-Sumner merged 61 commits into
mainfrom
claude/hardening-sweep

Conversation

@Jarred-Sumner
Copy link
Copy Markdown
Collaborator

General hardening pass tightening input validation, buffer bounds, and protocol handling across the runtime.

Areas touched (37 commits):

  • node:path (1)
  • node:buffer (1)
  • node:net (1)
  • node:http (2)
  • node:child_process (1)
  • http2 (1)
  • http (1)
  • fetch (1)
  • websocket (3)
  • Bun.serve (1)
  • bake / dev server (1)
  • sql / postgres (3)
  • sql / mysql (1)
  • sql (1)
  • valkey (1)
  • sqlite (1)
  • install / lockfile / patch (4)
  • resolver (1)
  • archive (1)
  • s3 (1)
  • sys (1)
  • glob (1)
  • css (1)
  • bundler (1)
  • router (1)
  • debugger (1)
  • webcore / blob / serialization (2)
  • webcrypto (1)

cargo check -p bun_bin and bun run rust:check-all (all 10 targets) pass.

@Jarred-Sumner Jarred-Sumner requested a review from alii as a code owner May 20, 2026 11:53
@robobun
Copy link
Copy Markdown
Collaborator

robobun commented May 20, 2026

Updated 6:22 PM PT - May 20th, 2026

@Jarred-Sumner, your commit 941e4b4 has 1 failures in Build #56485 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 31129

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

bun-31129 --bun

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR applies security hardening and protocol fixes: adds MySQL allowPublicKeyRetrieval control, caps parser depths and buffer sizes, tightens HTTP/WS/H2 validation, improves path and archive safety, fixes crypto/buffer indexing, and wires option propagation and tests.

Changes

MySQL allowPublicKeyRetrieval Feature

Layer / File(s) Summary
Error types, JSDoc, and option parsing
packages/bun-types/sql.d.ts, src/sql/mysql/protocol/AnyMySQLError.rs, src/js/internal/sql/shared.ts
Adds allowPublicKeyRetrieval to types, new PublicKeyRetrievalNotAllowed enum variant, and threads option through parseOptions.
JavaScript binding and pooled connection wiring
src/js/internal/sql/mysql.ts
Adds allowPublicKeyRetrieval parameter to Zig binding and forwards it from pooled connection creation.
JSC connection init and auth enforcement
src/sql_jsc/mysql/JSMySQLConnection.rs, src/sql_jsc/mysql/MySQLConnection.rs, src/sql_jsc/mysql/protocol/any_mysql_error_jsc.rs
JSC reads flag argument; MySQLConnection stores flag and refuses public-key retrieval over plain TCP unless allowed; maps error to JS code.
Test integration
test/js/sql/sql-mysql.auth.test.ts, test/js/sql/sql-mysql.test.ts
Tests updated to assert the negative case without the option and pass allowPublicKeyRetrieval: true for success.

Path Sanitization and Input Validation

Layer / File(s) Summary
Bundler and template path escaping
src/bundler/options.rs
Sanitizes leading .. segments in [dir] template output to _.._/ or _.._.
Package installation and lockfile safety
src/install/bin.rs, src/install/repository.rs, src/install/lockfile/bun.lock.rs
Normalize bin names (reject ./..), add is_safe_resolved_tag and early checkout guard, and enforce integrity/tag validation in lockfile parsing.
Archive extraction and patch safety
src/libarchive/lib.rs, src/patch/lib.rs
Normalize symlink targets to detect .. climbs; case-insensitive symlink traversal checks; validate patch paths before rename/patch/chmod.
Package resolver validation
src/resolver/package_json.rs
Percent-decode and lowercase segments, reject ., .., node_modules after normalization.

Protocol Depth and Size Limits

Layer / File(s) Summary
CSS nesting and glob expansion caps
src/css/css_parser.rs, src/glob/matcher.rs
Add MAX_NESTING_DEPTH to CSS parser and per-match BRACE_BRANCH_BUDGET for glob matching.
HTTP, WebSocket, and RESP size limits
src/http/lib.rs, src/http_jsc/websocket_client.rs, src/valkey/valkey_protocol.rs
Abort on oversized buffered headers; cap WebSocket inbound messages at 128MB; cap RESP bulk/blob lengths at 512MiB.

WebSocket and HTTP Protocol Validation

Layer / File(s) Summary
WebSocket handshake and header validation
packages/bun-uws/src/HttpParser.h, packages/bun-uws/src/HttpResponse.h, src/http_jsc/websocket_client/WebSocketUpgradeClient.rs, src/runtime/server/server_body.rs
Reject empty HTTP header names; copy Sec-WebSocket-Key into fixed buffer; require 24-byte key length and exact Sec-WebSocket-Version: 13.
Response header sanitization and Set-Cookie detection
src/runtime/server/RequestContext.rs, src/jsc/bindings/NodeHTTP.cpp
Content-Type emission rejects CR/LF/NUL in values; Set-Cookie handling gated on recognized header name.
PostgreSQL SASL and SSL enforcement
src/sql_jsc/postgres/PostgresSQLConnection.rs, src/sql_jsc/postgres/PostgresRequest.rs
SASL nonce prefix validation and expanded SSL-mode failure cases when TLS unavailable.

HTTP/2 Frame Parser Refactoring

Layer / File(s) Summary
Per-stream header-block tracking
src/runtime/api/bun/h2_frame_parser.rs
Add per-stream header_block_size and header_block_count and enforce header limits per-stream.
Stream allocation and lookup centralization
src/runtime/api/bun/h2_frame_parser.rs
New lookup_inbound_stream helper prevents creating streams for non-HEADERS frames.
Frame-specific validation and Zig parity
src/runtime/api/bun/h2_frame_parser.rs, src/runtime/api/bun/h2_frame_parser.zig
DATA/GOAWAY/HEADERS validated against local_settings.max_frame_size; PING adds session-memory ENHANCE_YOUR_CALM; PRIORITY/CONTINUATION validation/order changes.

Cryptographic and Buffer Safety

Layer / File(s) Summary
RSA, BIO, and OKP bounds
src/jsc/bindings/webcore/SerializedScriptValue.cpp, src/jsc/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
Fix RSA additional-prime indexing and use safe unsigned-length BIO checks; require 5 bytes before reading OKP OID+curve.
Buffer detachment and length clamping
src/jsc/bindings/JSBuffer.cpp
Re-check detachment after encoding parse and clamp write length to remaining bytes from offset.
Safe slices and UTF-16LE casts
src/semver/lib.rs, src/runtime/webcore/Blob.rs
Use bounds-checked slice lookups; truncate odd-length UTF-16LE BOM buffers before casting.

Blob Multipart and SQL Helpers

Layer / File(s) Summary
Multipart form-data percent-encoding
src/runtime/webcore/Blob.rs
Escape "/CR/LF in form-data name and filename; fall back to application/octet-stream for unsafe content-types.
SQL savepoint and S3 upload_id parsing
src/js/bun/sql.ts, src/runtime/webcore/s3/multipart.rs
Sanitize savepoint names to [A-Za-z0-9_]; compute UploadId slice safely from tag length.

Dev Server and Node API Security

Layer / File(s) Summary
DNS rebinding protection
src/runtime/bake/DevServer.rs
Validate Host header for internal dev routes and return 403 for disallowed hosts.
BlockList structured-clone safety
src/runtime/node/net/BlockList.rs
Registry of serialized BlockList addresses validated on deserialization; entries removed on destroy.
Windows spawn validation and debugger UUID
src/js/node/child_process.ts, src/js/internal/debugger.ts
Reject .bat/.cmd non-shell spawns on win32; use crypto.randomUUID() for randomId.

Supporting Fixes and Type Safety

Layer / File(s) Summary
Iterator, dynamic downcasts, and index widening
src/jsc/bindings/node/http/JSConnectionsList.cpp, src/jsc/bindings/sqlite/JSSQLStatement.cpp, src/http_types/URLPath.rs
Refactor iterators, use dynamicDowncast, and widen index types to i32.
Runtime assertions and tmpfile exclusivity
src/runtime/node/path.rs, src/sys/tmp.rs
Add UNC buffer bounds asserts and include O::EXCL when creating tmpfiles.
Tests
test/cli/inspect/inspect.test.ts
Tighten inspect pathname regex to allow hyphens.
  • Possibly related PRs:

  • Suggested reviewers:

    • alii
    • RiskyMH
    • dylan-conway
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: a comprehensive hardening pass tightening input validation and bounds across multiple subsystems.
Description check ✅ Passed The description is well-structured, listing areas touched, commit count, and verification steps performed, providing sufficient context despite not fully matching the template sections.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

Caution

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

⚠️ Outside diff range comments (4)
src/sql_jsc/postgres/PostgresRequest.rs (1)

468-482: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Start plaintext startup after an SSLRequest refusal.

When this branch accepts b'N' for a non-failing mode like SslMode::Prefer, it only records SslNotAvailable and continues. At that point the server is waiting for the client startup packet, but start() is only reached from on_open() or after setup_tls(), so the connection never progresses and eventually times out instead of falling back to plain TCP. Kick off the normal startup path here and return after doing so.

🤖 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/sql_jsc/postgres/PostgresRequest.rs` around lines 468 - 482, In the b'N'
branch where you set connection.tls_status to TlsStatus::SslNotAvailable,
instead of just continuing, initiate the normal plaintext startup path (call the
connection/start() entry used by on_open()/setup_tls()) when the SSL mode allows
fallback (e.g., SslMode::Prefer) and then return Ok(()) so the connection
proceeds; specifically, replace the trailing continue with code that triggers
the startup (invoke start() or the same method used to begin plain TCP startup)
and return, leaving the existing failure handling for Require/Verify modes
unchanged.
src/js/internal/sql/mysql.ts (1)

255-289: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Classify PublicKeyRetrievalNotAllowed as non-retryable auth failure.

This new path introduces ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED, but retry() still treats unknown codes as retryable. That can cause repeated failed reconnect attempts instead of failing fast.

Proposed fix
       switch (this.storedError?.code) {
         case "ERR_MYSQL_PASSWORD_REQUIRED":
         case "ERR_MYSQL_MISSING_AUTH_DATA":
         case "ERR_MYSQL_FAILED_TO_ENCRYPT_PASSWORD":
         case "ERR_MYSQL_INVALID_PUBLIC_KEY":
+        case "ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED":
         case "ERR_MYSQL_UNSUPPORTED_PROTOCOL_VERSION":
         case "ERR_MYSQL_UNSUPPORTED_AUTH_PLUGIN":
         case "ERR_MYSQL_AUTHENTICATION_FAILED":
           // we can't retry these are authentication errors
           return false;
🤖 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/mysql.ts` around lines 255 - 289, The new
ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED error (exposed as
PublicKeyRetrievalNotAllowed) must be treated as a non-retryable authentication
failure by the connection retry logic; update the retry()/isTransientError()
handling to explicitly mark ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED (and the
PublicKeyRetrievalNotAllowed error class/identifier) as non-retryable so the
reconnect loop fails fast instead of repeatedly retrying on this auth error.
src/jsc/bindings/webcore/SerializedScriptValue.cpp (1)

3985-3991: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add length bounds check and explicit cast before calling BIO_new_mem_buf.

The length parameter is uint64_t but BIO_new_mem_buf expects an int (or ossl_ssize_t depending on the cryptography library). Passing values exceeding INT_MAX will silently truncate, causing the PEM buffer to be misparsed. Clamp the length and explicitly cast to prevent overflow.

Suggested fix
     bool read(BIO** bio, uint64_t length)
     {
+        if (length > std::numeric_limits<int>::max())
+            return false;
         if (static_cast<uint64_t>(m_end - m_ptr) < length)
             return false;
-        *bio = BIO_new_mem_buf(m_ptr, length);
+        *bio = BIO_new_mem_buf(m_ptr, static_cast<int>(length));
         if (!*bio)
             return false;
         m_ptr += length;
         return true;
     }
🤖 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/jsc/bindings/webcore/SerializedScriptValue.cpp` around lines 3985 - 3991,
The code calls BIO_new_mem_buf(m_ptr, length) where length is uint64_t; add an
explicit bounds check against INT_MAX and the available buffer size (using m_end
and m_ptr) before calling BIO_new_mem_buf, clamp length to int if necessary, and
pass a safe explicit cast (e.g., static_cast<int>(clampedLength)) to
BIO_new_mem_buf; update the existing early-return condition to fail if length >
static_cast<uint64_t>(INT_MAX) or if static_cast<uint64_t>(m_end - m_ptr) <
length, then advance m_ptr by the original length on success and ensure *bio is
created with the int-casted/clamped length.
src/runtime/node/net/BlockList.rs (1)

429-447: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Take the ref while the registry lock is still held.

Line 429 validates the pointer, but the guard is dropped before Line 446 bumps the refcount. A concurrent BlockList__onStructuredCloneDestroy can remove the last entry and free this in that gap, turning the later ref_() into a use-after-free. Keep lookup and ref acquisition in the same critical section.

Suggested fix
-        if !SERIALIZED_REFS.lock().contains(&int) {
+        let refs = SERIALIZED_REFS.lock();
+        if !refs.contains(&int) {
             return Err(global.throw(format_args!(
                 "BlockList.onStructuredCloneDeserialize failed"
             )));
         }
 
         let this: *mut Self = int as *mut Self;
@@
-        unsafe {
-            (*this).ref_();
-            Ok(Self::to_js_ptr(this, global))
-        }
+        unsafe { (*this).ref_(); }
+        drop(refs);
+        unsafe { Ok(Self::to_js_ptr(this, global)) }

Also applies to: 458-468

🤖 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/runtime/node/net/BlockList.rs` around lines 429 - 447, The pointer
validation currently checks SERIALIZED_REFS.lock().contains(&int) but releases
the lock before calling (*this).ref_(), which allows a concurrent
BlockList__onStructuredCloneDestroy to free the object; fix by acquiring the
SERIALIZED_REFS lock and performing the contains check and the ref bump (call to
(*this).ref_() or equivalent) while the lock is still held, then drop the lock
and return Self::to_js_ptr(this, global); apply the same change to the analogous
block later (lines 458-468) so lookup and ref acquisition occur in the same
critical section and avoid a use-after-free.
🤖 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/repository.rs`:
- Around line 314-321: is_safe_resolved_tag currently accepts the
single-component names "." and ".." which can bypass path-checking; update the
function is_safe_resolved_tag to explicitly reject those two values (e.g.,
return false if resolved == b"." or resolved == b"..") in addition to the
existing checks so they cannot pass the checkout guard later.

In `@src/js/bun/sql.ts`:
- Line 747: The thrown error in onTransactionConnected uses the wrong variable
name in the message: update the $ERR_INVALID_ARG_VALUE call so it references
savepoint_callback (the actual variable being validated) instead of callback;
locate the validation in onTransactionConnected where savepoint_callback is
checked and change the argument name in the error constructor to
"savepoint_callback" so the error message reflects the correct variable being
validated.

In `@src/js/internal/sql/shared.ts`:
- Line 868: The property assignment uses truthy coercion
(allowPublicKeyRetrieval: !!options.allowPublicKeyRetrieval) which accepts
strings/numbers; change it to only accept an actual boolean by setting
allowPublicKeyRetrieval to true only when options.allowPublicKeyRetrieval is the
boolean true and otherwise set it to false (or use a typeof check to accept only
boolean values); update the assignment in shared.ts where
allowPublicKeyRetrieval is constructed so non-boolean inputs do not enable the
option.

In `@src/jsc/bindings/JSBuffer.cpp`:
- Around line 2431-2437: After recomputing currentByteLength in JSBuffer::...
(use castedThis->byteLength(), currentByteLength, currentRemaining, offset,
length), add an early check that if offset >= currentByteLength you
short-circuit and return (do not proceed to call parseEncoding() /
writeToBuffer()), because a post-parse resize can make offset out-of-range even
for a zero-length write; this prevents building vector() + offset with an
invalid pointer.

In `@src/libarchive/lib.rs`:
- Around line 1349-1356: The code currently applies case-insensitive
symlink-prefix matching for all Unix by calling
strings::eql_case_insensitive_ascii_check_length on prefix vs created_symlinks,
which causes incorrect skipping on case-sensitive filesystems; change the logic
in the block that references prefix and created_symlinks so the case-insensitive
comparison is only used when running on a case-insensitive filesystem (e.g.,
guard with a platform/filesystem check such as cfg!(target_os = "macos") or an
existing is_case_insensitive_fs() helper) and otherwise perform an exact
byte-wise match (use direct equality) instead of
strings::eql_case_insensitive_ascii_check_length.

In `@src/runtime/api/bun/h2_frame_parser.rs`:
- Around line 4064-4080: The code double-finalizes END_STREAM on the last
CONTINUATION: when stream.end_after_headers was already handled by
handle_headers_frame, this block should only clear
stream.is_waiting_more_headers and not re-run the END_STREAM transition. Modify
the block that checks stream.is_waiting_more_headers and
stream.end_after_headers so that if stream.state ==
StreamState::HALF_CLOSED_REMOTE you simply clear stream.is_waiting_more_headers
(and call identifier.ensure_still_alive()), but do NOT set stream.state =
StreamState::CLOSED, do NOT call stream.free_resources::<false>(self), and do
NOT dispatch JSH2FrameParser::Gc::onStreamEnd; only perform the state
transition/free/dispatch when the stream hasn't already been moved to
HALF_CLOSED_REMOTE by handle_headers_frame.
- Around line 4041-4054: The code checks inbound CONTINUATION frame size against
remote_settings (via the `settings` variable) but should use the local limit;
change the comparison to use `self.local_settings.get().max_frame_size` (or set
`settings` from `local_settings.get()` instead of
`remote_settings.get().unwrap_or_else(|| self.local_settings.get())`) so the `if
frame.length > ...` uses the local max; keep the existing `send_go_away` call
(`send_go_away`, `ErrorCode::FRAME_SIZE_ERROR`, `last_stream_id.get()`)
unchanged.
- Around line 4488-4505: lookup_inbound_stream is currently allocating a new
Stream for unknown inbound HEADERS by calling handle_received_stream_id even
when this parser is a client; change the logic so unknown HEADERS only allocate
when is_server is true. Specifically, in lookup_inbound_stream (and before
calling handle_received_stream_id) ensure you check self.is_server.get() and
return None for client parsers instead of creating a new stream (leave server
behavior unchanged); this prevents clients from accepting peer-initiated HEADERS
that should be protocol errors given no PUSH_PROMISE handling in dispatch_frame.

In `@src/runtime/bake/DevServer.rs`:
- Around line 1421-1477: The HMR websocket upgrade path isn't checking
is_allowed_dev_host, so modify the websocket upgrade handler (where
on_websocket_upgrade / the app.ws(...) HMR registration handles incoming upgrade
requests) to call is_allowed_dev_host(&dev, req) and reject disallowed hosts by
invoking host_forbidden(resp) (or sending a 403 and closing the upgrade) before
proceeding with the existing HMR handshake; ensure the same DevServer instance
used by dev_route_tramp is passed/accessible so the hostname comparison uses the
server config.

In `@src/runtime/node/net/BlockList.rs`:
- Around line 38-43: The current SERIALIZED_REFS guard storing raw pointer
addresses is vulnerable to allocator-reuse replay; replace the pointer-based
registry with a per-serialization capability (unique token): add a unique
id/nonce field to BlockList instances (or generate a fresh random/monotonic
token at serialize time), record that token in the serialized payload instead of
the raw usize, and change SERIALIZED_REFS to map tokens → weak references (or
ownership markers); update the lookup/contains logic (uses of
SERIALIZED_REFS.contains(&int) and the code in
BlockList__onStructuredCloneDestroy) to validate the token against the registry
during deserialize and to remove the token on destroy. Apply the same
token-based change to the other referenced spots (lines ~390-391, ~400,
~429-433, ~458-463) so all serialization/deserialization paths use the
capability token rather than a raw pointer address.

In `@src/runtime/node/path.rs`:
- Line 3298: Change the assert! guarding tmp_buf length to an explicit bounds
check that returns an error instead of panicking: validate that buf_size <=
tmp_buf.len() (using first_part_len and slice_len / MAX_PATH_SIZE_UPPER inputs)
before any unsafe pointer writes in the UNC device string construction code that
manipulates tmp_buf and buf_size; if the check fails, return an appropriate Err
(matching the function's Result error type, e.g., io::Error or the crate's
PathError) with a clear message about the path being too long. Apply the same
replacement for the second assertion site that checks tmp_buf length (the other
place using slice_len) so both potential panics become graceful errors.

In `@src/runtime/webcore/Blob.rs`:
- Around line 2667-2673: The early-return path in to_string_with_bytes that
handles a UTF-16LE BOM skips applying Lifetime::Transfer, so the Blob's backing
store isn't detached; before constructing OwnedString/returning (the block that
does OwnedString::new(BunString::clone_utf16(...)) and out.to_js(global)),
invoke the same detachment logic used by other transfer paths (i.e. apply
Lifetime::Transfer to the source Blob/backing store — reuse the existing helper
or code block in this file that sets Lifetime::Transfer) so the buffer is
detached from the original Blob prior to creating and returning the JS string.

In `@src/semver/lib.rs`:
- Around line 530-533: The current comparison uses unwrap_or_default() on both
this_buf.get(...) and that_buf.get(...), which makes two invalid/out-of-bounds
ranges compare equal; change it to first check whether both ranges are valid
(this_buf.get(a_off..a_off + a_len) and that_buf.get(b_off..b_off + b_len)) and
if either returns None immediately return false, otherwise pass the two valid
slices into strings::eql; reference the variables and call site: this_buf,
that_buf, a_off, a_len, b_off, b_len and strings::eql.

In `@test/js/sql/sql-mysql.auth.test.ts`:
- Around line 40-43: Add a negative-path test that constructs SQL without the
allowPublicKeyRetrieval flag to assert the secure default denies public key
retrieval: call new SQL(...) with the same MySQL URL but omit
allowPublicKeyRetrieval inside the test (mirroring the positive test using the
same container.host/container.port), attempt a query or connection inside an
await/throws expectation, and assert it rejects with error code
"ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED"; refer to the SQL constructor used
in the positive test and the test helper/container variables so the new test
mirrors setup but expects the specified error.

---

Outside diff comments:
In `@src/js/internal/sql/mysql.ts`:
- Around line 255-289: The new ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED error
(exposed as PublicKeyRetrievalNotAllowed) must be treated as a non-retryable
authentication failure by the connection retry logic; update the
retry()/isTransientError() handling to explicitly mark
ERR_MYSQL_PUBLIC_KEY_RETRIEVAL_NOT_ALLOWED (and the PublicKeyRetrievalNotAllowed
error class/identifier) as non-retryable so the reconnect loop fails fast
instead of repeatedly retrying on this auth error.

In `@src/jsc/bindings/webcore/SerializedScriptValue.cpp`:
- Around line 3985-3991: The code calls BIO_new_mem_buf(m_ptr, length) where
length is uint64_t; add an explicit bounds check against INT_MAX and the
available buffer size (using m_end and m_ptr) before calling BIO_new_mem_buf,
clamp length to int if necessary, and pass a safe explicit cast (e.g.,
static_cast<int>(clampedLength)) to BIO_new_mem_buf; update the existing
early-return condition to fail if length > static_cast<uint64_t>(INT_MAX) or if
static_cast<uint64_t>(m_end - m_ptr) < length, then advance m_ptr by the
original length on success and ensure *bio is created with the
int-casted/clamped length.

In `@src/runtime/node/net/BlockList.rs`:
- Around line 429-447: The pointer validation currently checks
SERIALIZED_REFS.lock().contains(&int) but releases the lock before calling
(*this).ref_(), which allows a concurrent BlockList__onStructuredCloneDestroy to
free the object; fix by acquiring the SERIALIZED_REFS lock and performing the
contains check and the ref bump (call to (*this).ref_() or equivalent) while the
lock is still held, then drop the lock and return Self::to_js_ptr(this, global);
apply the same change to the analogous block later (lines 458-468) so lookup and
ref acquisition occur in the same critical section and avoid a use-after-free.

In `@src/sql_jsc/postgres/PostgresRequest.rs`:
- Around line 468-482: In the b'N' branch where you set connection.tls_status to
TlsStatus::SslNotAvailable, instead of just continuing, initiate the normal
plaintext startup path (call the connection/start() entry used by
on_open()/setup_tls()) when the SSL mode allows fallback (e.g., SslMode::Prefer)
and then return Ok(()) so the connection proceeds; specifically, replace the
trailing continue with code that triggers the startup (invoke start() or the
same method used to begin plain TCP startup) and return, leaving the existing
failure handling for Require/Verify modes unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a7b5e83-7476-4c09-bff3-359611852442

📥 Commits

Reviewing files that changed from the base of the PR and between b3d086e and 4b6b4c5.

📒 Files selected for processing (49)
  • packages/bun-types/sql.d.ts
  • packages/bun-uws/src/HttpParser.h
  • packages/bun-uws/src/HttpResponse.h
  • src/bundler/options.rs
  • src/css/css_parser.rs
  • src/glob/matcher.rs
  • src/http/lib.rs
  • src/http_jsc/websocket_client.rs
  • src/http_jsc/websocket_client/WebSocketUpgradeClient.rs
  • src/http_types/URLPath.rs
  • src/install/bin.rs
  • src/install/lockfile/bun.lock.rs
  • src/install/repository.rs
  • src/js/bun/sql.ts
  • src/js/internal/debugger.ts
  • src/js/internal/sql/mysql.ts
  • src/js/internal/sql/postgres.ts
  • src/js/internal/sql/shared.ts
  • src/js/node/child_process.ts
  • src/jsc/bindings/JSBuffer.cpp
  • src/jsc/bindings/NodeHTTP.cpp
  • src/jsc/bindings/node/http/JSConnectionsList.cpp
  • src/jsc/bindings/sqlite/JSSQLStatement.cpp
  • src/jsc/bindings/webcore/SerializedScriptValue.cpp
  • src/jsc/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
  • src/libarchive/lib.rs
  • src/patch/lib.rs
  • src/resolver/package_json.rs
  • src/runtime/api/bun/h2_frame_parser.rs
  • src/runtime/api/bun/h2_frame_parser.zig
  • src/runtime/bake/DevServer.rs
  • src/runtime/node/net/BlockList.rs
  • src/runtime/node/path.rs
  • src/runtime/server/RequestContext.rs
  • src/runtime/server/server_body.rs
  • src/runtime/webcore/Blob.rs
  • src/runtime/webcore/s3/multipart.rs
  • src/semver/lib.rs
  • src/sql/mysql/protocol/AnyMySQLError.rs
  • src/sql_jsc/mysql/JSMySQLConnection.rs
  • src/sql_jsc/mysql/MySQLConnection.rs
  • src/sql_jsc/mysql/protocol/any_mysql_error_jsc.rs
  • src/sql_jsc/postgres/PostgresRequest.rs
  • src/sql_jsc/postgres/PostgresSQLConnection.rs
  • src/sys/tmp.rs
  • src/valkey/valkey_protocol.rs
  • test/cli/inspect/inspect.test.ts
  • test/js/sql/sql-mysql.auth.test.ts
  • test/js/sql/sql-mysql.test.ts

Comment thread src/install/repository.rs
Comment thread src/js/bun/sql.ts Outdated
Comment thread src/js/internal/sql/shared.ts Outdated
Comment thread src/jsc/bindings/JSBuffer.cpp
Comment thread src/libarchive/lib.rs
Comment thread src/runtime/node/net/BlockList.rs
Comment thread src/runtime/node/path.rs Outdated
Comment thread src/runtime/webcore/Blob.rs
Comment thread src/semver/lib.rs Outdated
Comment thread test/js/sql/sql-mysql.auth.test.ts
Comment thread src/js/node/child_process.ts Outdated
Comment thread src/runtime/api/bun/h2_frame_parser.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/runtime/node/path.rs (1)

3298-3323: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a bounds guard before the separator write at Line 3314.

The new checks protect the two copy operations, but there is still a panic edge: if Line 3298 leaves buf_size == tmp_buf.len(), the write at Line 3314 (tmp_buf[buf_offset]) is out of bounds before Line 3318 is reached.

🛠️ Minimal fix
                             buf_offset = buf_size;
-                            buf_size += 1;
+                            if buf_size >= tmp_buf.len() {
+                                return Err(bun_sys::Error::from_code(
+                                    bun_sys::E::ENAMETOOLONG,
+                                    bun_sys::Tag::TODO,
+                                ));
+                            }
+                            buf_size += 1;
                             tmp_buf[buf_offset] = T::from_u8(CHAR_BACKWARD_SLASH);

As per coding guidelines: “Don't .unwrap() a fallible path that user input or the OS can hit at runtime — return the error.”

🤖 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/runtime/node/path.rs` around lines 3298 - 3323, The write to tmp_buf at
tmp_buf[buf_offset] (around the separator insertion) can panic when buf_size ==
tmp_buf.len(); add a bounds guard before writing the separator: check whether
buf_size < tmp_buf.len() (using the same error path as other checks) and return
bun_sys::Error::from_code(bun_sys::E::ENAMETOOLONG, bun_sys::Tag::TODO) if not,
then update buf_offset/buf_size and write CHAR_BACKWARD_SLASH; ensure the check
uses the same tmp_buf, buf_size, buf_offset variables and error construction as
the surrounding copy-bound checks to keep behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/bake/DevServer.rs`:
- Around line 1604-1606: Remove the dead inherent method named
on_web_socket_upgrade from the DevServer impl: delete the entire inherent method
body so only the trait-based implementation WebSocketUpgradeServer<SSL> (which
already applies is_allowed_dev_host) remains; verify there are no call sites
referencing the inherent on_web_socket_upgrade and run tests/build to ensure
compilation succeeds after removing the duplicate upgrade logic.

---

Outside diff comments:
In `@src/runtime/node/path.rs`:
- Around line 3298-3323: The write to tmp_buf at tmp_buf[buf_offset] (around the
separator insertion) can panic when buf_size == tmp_buf.len(); add a bounds
guard before writing the separator: check whether buf_size < tmp_buf.len()
(using the same error path as other checks) and return
bun_sys::Error::from_code(bun_sys::E::ENAMETOOLONG, bun_sys::Tag::TODO) if not,
then update buf_offset/buf_size and write CHAR_BACKWARD_SLASH; ensure the check
uses the same tmp_buf, buf_size, buf_offset variables and error construction as
the surrounding copy-bound checks to keep behavior consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b192d293-15d7-4e9a-b9f1-2601b4c91614

📥 Commits

Reviewing files that changed from the base of the PR and between cd7533f and ce9a8ff.

📒 Files selected for processing (8)
  • src/install/repository.rs
  • src/js/bun/sql.ts
  • src/js/internal/sql/shared.ts
  • src/runtime/api/bun/h2_frame_parser.rs
  • src/runtime/bake/DevServer.rs
  • src/runtime/node/path.rs
  • src/semver/lib.rs
  • test/js/sql/sql-mysql.auth.test.ts

Comment thread src/runtime/bake/DevServer.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/lockfile/bun.lock.rs`:
- Around line 2490-2501: The code currently assigns pkg.meta.integrity =
Integrity::parse(integrity_str) even when the parsed integrity tag is
unsupported, then only emits a warning; change this so unsupported/malformed
integrity is not kept: either only assign pkg.meta.integrity when integrity_str
is non-empty and Integrity::parse(...).tag.is_supported(), or after detecting an
unsupported tag (the branch that calls log.add_warning with integrity_expr.loc)
reset pkg.meta.integrity = Integrity::default() so the malformed value is not
serialized back out; update the logic around Integrity::parse, integrity_str,
pkg.meta.integrity, and the warning branch to implement this.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b42ecf63-4848-4448-b38f-4a485f528671

📥 Commits

Reviewing files that changed from the base of the PR and between ce9a8ff and 43a2c90.

📒 Files selected for processing (3)
  • src/http/lib.rs
  • src/install/lockfile/bun.lock.rs
  • test/regression/issue/malformed-integrity-base64.test.ts

Comment thread src/install/lockfile/bun.lock.rs
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

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

  • 🔴 src/runtime/api/bun/h2_frame_parser.rs:4498-4506 — On the client side (is_server == false), this still falls through to handle_received_stream_id() for a HEADERS frame on an unknown stream id, so a server can allocate arbitrary Stream objects and fire onStreamStart/onStreamHeaders on the client. Client request streams are always created locally before the response arrives and dispatch_frame doesn't implement PUSH_PROMISE, so an unknown HEADERS id on the client is always a protocol error — add || !self.is_server.get() to the early return at line 4498.

    Extended reasoning...

    What the gap is

    lookup_inbound_stream() is the new helper this PR adds to gate per-frame stream allocation. Its doc comment says "Only a HEADERS frame may allocate new stream state (RFC 9113 §5.1)", and at line 4498 it correctly returns None for any non-HEADERS frame on an unknown id. But the only role-specific check is the parity guard at line 4502, which fires only when self.is_server.get() is true (rejecting even-numbered ids that a client cannot legally open). When is_server == false, an inbound HEADERS frame on an unknown stream id falls straight through to handle_received_stream_id(stream_identifier) at line 4505.

    Why that's wrong on the client

    On the client side, every request stream is created locally — the JS request() path calls handle_received_stream_id directly and inserts the stream into self.streams before the request is sent — so by the time the server's response HEADERS arrives, the lookup at line 4495 hits and returns the existing entry. Per RFC 9113 §5.1.1, the only way a server may initiate a stream is via PUSH_PROMISE on an even-numbered id, never via a bare HEADERS frame, and dispatch_frame in this file does not implement PUSH_PROMISE at all. Therefore a client receiving HEADERS on a stream id that is not already in self.streams is always a protocol error from a malicious or buggy server.

    What handle_received_stream_id does with it

    handle_received_stream_id (lines 4434–4485) has no is_server check of its own. For an unknown id it unconditionally:

    1. bumps last_stream_id,
    2. heap-allocates a new Stream and inserts it into self.streams, and
    3. dispatches the JS onStreamStart callback.

    Control then returns to dispatch_framehandle_headers_frame, which will HPACK-decode the header block and dispatch onStreamHeaders for a stream the client never asked for.

    Step-by-step proof

    1. Client creates an H2FrameParser with is_server = false and connects to a server.
    2. Client sends a request on stream 1; self.streams now contains {1}.
    3. The server sends a HEADERS frame with stream_identifier = 7 (or any odd id the client never opened).
    4. read_bytes parses the frame header and calls lookup_inbound_stream(7, HTTP_FRAME_HEADERS).
    5. Line 4492: 7 != 0 → continue. Line 4495: 7 ∉ self.streams → no early return. Line 4498: frame type is HEADERS → continue. Line 4502: is_server.get() is false, so the parity check is skipped entirely. Line 4505: handle_received_stream_id(7) runs.
    6. A new Stream is allocated, inserted into self.streams, and onStreamStart fires on the client for a server-initiated stream. handle_headers_frame then decodes the block and calls onStreamHeaders.

    A malicious server can repeat this with arbitrary ids to allocate unbounded Stream objects on the client (each is a heap-boxed struct plus a hash-map entry) and to surface unsolicited onStreamStart events into JS, instead of the connection being torn down with a PROTOCOL_ERROR.

    Not a regression, but an incomplete fix

    Before this PR, read_bytes called handle_received_stream_id directly for every inbound frame type (and the .zig sibling still does), so the new function is a strict improvement — DATA, RST_STREAM, WINDOW_UPDATE, etc. on unknown ids no longer allocate. But the PR's stated purpose is exactly this hardening, the function's own RFC-citing doc comment promises it, and the client-side HEADERS path is the obvious remaining gap. Since the parity guard already encodes "who is allowed to open a stream from the wire" for the server role, the symmetric client-role check belongs in the same place.

    Fix

    if frame_type != FrameType::HTTP_FRAME_HEADERS as u8 || !self.is_server.get() {
        return None;
    }

    (after which the now-redundant self.is_server.get() && on the parity check at line 4502 can be dropped). Returning None routes the frame into handle_headers_frame with stream_ = None, which already sends GOAWAY PROTOCOL_ERROR ("Headers frame on connection stream") — the right outcome for a server-initiated HEADERS.

    🔬 also observed by coderabbitai

  • 🔴 src/runtime/api/bun/h2_frame_parser.rs:4064-4081 — When a HEADERS frame carries END_STREAM but not END_HEADERS, handle_headers_frame already moves the stream to HALF_CLOSED_REMOTE and dispatches onStreamEnd; the new CONTINUATION block then sees end_after_headers still true, finds state == HALF_CLOSED_REMOTE, transitions it to CLOSED + free_resources(), and dispatches onStreamEnd a second time. That double-fires the callback and tears the stream down before the local side can send its response. The state check at L4069 should mirror handle_headers_frame's HALF_CLOSED_LOCAL → CLOSED (L4204), not HALF_CLOSED_REMOTE → CLOSED — or the whole block should be skipped since the HEADERS handler already performed the transition.

    Extended reasoning...

    What the bug is

    The new END_STREAM finalization block in handle_continuation_frame (h2_frame_parser.rs:4064-4081) runs in addition to the existing finalization in handle_headers_frame (4196-4215), and uses an inverted state check. The result is that for any HEADERS frame split across CONTINUATION frames with END_STREAM set, onStreamEnd fires twice and the stream is freed while the local side still owes a response.

    How it manifests — step-by-step trace

    Consider a server receiving a request whose headers span two frames:

    Frame 1: HEADERS, flags = END_STREAM (no END_HEADERS), stream 1

    handle_headers_frame runs:

    • L4186: stream.end_after_headers = true (END_STREAM is set)
    • L4194-4195: stream.is_waiting_more_headers = true (END_HEADERS is not set)
    • L4196: if stream.end_after_headers → enters the block
    • L4200-4201: is_waiting_more_headers is true → stream.state = HALF_CLOSED_REMOTE
    • L4211: dispatches onStreamEnd (first fire)

    Frame 2: CONTINUATION, flags = END_HEADERS, stream 1

    handle_continuation_frame runs:

    • L4064-4065: stream.is_waiting_more_headers = false (END_HEADERS is set)
    • L4066: !is_waiting_more_headers && end_after_headers → both true (the PR removed the line that overwrote end_after_headers from the CONTINUATION frame's flags, so it stays true from step 1 — which is correct per spec, since CONTINUATION has no END_STREAM flag)
    • L4069: stream.state == HALF_CLOSED_REMOTEtrue (just set in step 1)
    • L4070-4072: stream.state = CLOSED; stream.free_resources::<false>(self)
    • L4076: dispatches onStreamEnd (second fire)

    Why this is a regression introduced by this PR

    Before this PR, the CONTINUATION handler did stream.end_after_headers = frame.flags & END_STREAM != 0 before the finalization block. Per RFC 9113 §6.10, CONTINUATION frames define only the END_HEADERS flag, so a well-formed peer never sets bit 0 on a CONTINUATION frame, and end_after_headers was always overwritten to false. The finalization block was therefore dead code in practice. This PR (correctly) removed that overwrite so end_after_headers is preserved from the HEADERS frame — but that makes the previously-dead block live, and exposes its inverted state check.

    Why nothing else prevents it

    handle_headers_frame is the only other place that touches this state, and it has already run by the time the CONTINUATION arrives. There is no "already-dispatched" guard; the CONTINUATION block re-evaluates from scratch. And because the HEADERS handler put the stream into exactly HALF_CLOSED_REMOTE, the CONTINUATION handler's HALF_CLOSED_REMOTE → CLOSED arm is guaranteed to match.

    Impact

    • Double onStreamEnd: JS-side stream listeners see two end events for one stream.
    • Premature close: HALF_CLOSED_REMOTE means "the peer is done sending; we still may send". Transitioning it to CLOSED and calling free_resources() tears the stream down before the local endpoint (e.g. an HTTP/2 server) has sent its response headers/body. Compare with handle_headers_frame L4204, which only goes CLOSED from HALF_CLOSED_LOCAL (i.e. when we had already finished sending and the peer's END_STREAM completes the pair).
    • This affects the common case of any request whose header block exceeds one frame and has no body (e.g. a large-cookie GET request), so it is not an edge case.

    Fix

    Either:

    1. Drop the finalization block at L4066-4081 entirely — handle_headers_frame already performed the END_STREAM transition and dispatched onStreamEnd when it set is_waiting_more_headers = true; the CONTINUATION handler only needs to clear is_waiting_more_headers. Or,
    2. Move the END_STREAM transition out of handle_headers_frame's split-header path so it happens exactly once when END_HEADERS finally arrives, and fix the CONTINUATION state check to match L4204 (HALF_CLOSED_LOCAL → CLOSED, else → HALF_CLOSED_REMOTE).

    Option 1 is the minimal fix; option 2 is the spec-cleaner one (RFC 9113 §8.1: a header block is processed as a unit when END_HEADERS is received).

    🔬 also observed by coderabbitai

Comment thread src/runtime/bake/DevServer.rs
Comment thread src/js/node/child_process.ts
Comment thread src/runtime/api/bun/h2_frame_parser.rs
Comment thread src/jsc/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
Comment thread src/jsc/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
Comment thread src/js/internal/sql/postgres.ts
@Jarred-Sumner Jarred-Sumner force-pushed the claude/hardening-sweep branch from 08338d7 to 941e4b4 Compare May 20, 2026 23:58
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

All of my earlier inline findings have been addressed and this revision passes clean, but given the breadth (50 files, 26 subsystems) and the security-sensitive surfaces touched — auth handshakes, webcrypto DER parsing, structured-clone deserialization, HTTP/2 framing, archive extraction — this still warrants a human sign-off.

Extended reasoning...

Overview

This is a 37-commit hardening sweep across 50 files: MySQL allowPublicKeyRetrieval gating, Postgres SASL nonce/SSL-mode checks, HTTP/2 per-stream header accounting and inbound-stream allocation rules, WebSocket key/version validation, uWS empty-header-name rejection, DNS-rebinding guard for the bake DevServer (HTTP + HMR upgrade), archive symlink-traversal normalization, patch/install path-component validation, package.json exports percent-decode segment check, CSS nesting / glob brace budgets, Valkey RESP blob caps, Buffer.write post-reentrancy revalidation, SerializedScriptValue RSA prime indexing + BIO length check, OKP DER bounds, BlockList structured-clone pointer registry + nonce, multipart name escaping, S3 UploadId slice fix, child_process .bat/.cmd rejection, and assorted bounds tightening.

Security risks

Nearly every change here is security-relevant by design. The highest-leverage areas are: (1) the BlockList structured-clone path, which gates dereference of an attacker-supplied pointer behind a registry + per-instance nonce; (2) MySQL public-key retrieval now default-deny over plain TCP (behavioral change with new error code); (3) the H2 inbound-stream lookup refactor, which changes which frames can allocate stream state; (4) DevServer Host-header allowlisting, which now also covers the HMR WebSocket upgrade; (5) archive symlink normalization switching from a fake-root join to relative ..-preserving normalization with case-insensitive prefix match. Each of these went through at least one review round and the fixes look right, but they are exactly the kind of changes that benefit from a second pair of human eyes.

Level of scrutiny

High. This PR directly modifies auth flows, crypto key parsing, protocol state machines, and path-traversal defenses in production code paths. It also introduces user-visible behavioral changes (MySQL default-deny, Windows .bat/.cmd EINVAL, Postgres array-type allowlist) that carry compatibility risk. None of this fits the "simple/mechanical/obvious" bar for bot approval.

Other factors

The PR has been through extensive iterative review: CodeRabbit raised ~14 findings and I raised 8 inline issues across four passes; the author addressed all of them (CONTINUATION frame-size source, spawnSync error-delivery contract, HMR upgrade host check, OKP DER bounds in both importPkcs8 and importSpki, redundant is_server guard, BlockList nonce, postgres array-type allowlist widening, etc.), and every thread is resolved. cargo check and rust:check-all pass per the description, and the CI build is running. The current bug-hunting pass surfaced nothing new. That's a good signal, but the sheer surface area and the fact that several fixes required multiple iterations argue for human review before merge.

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