Skip to content

Error on URLs with an unparseable port instead of silently connecting to 80/443 - #37003

Open
robobun wants to merge 1 commit into
mainfrom
farm/106ddf9e/invalid-url-port
Open

robobun wants to merge 1 commit into
mainfrom
farm/106ddf9e/invalid-url-port

Conversation

@robobun

@robobun robobun commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A URL port that fails to parse as a u16 (:107688, :4295009448, :99999, :9000abc) silently collapses into the scheme default: URL::get_port_auto() (src/url/lib.rs) maps "present but unparseable" to None and then substitutes 80/443. Everything that goes through the WHATWG parser first (fetch(url), fetch(url, { proxy }), new WebSocket) rejects these strings, but two public surfaces skip it:

  • S3Client endpoint: the SigV4-signed request is sent to port 80/443 of the endpoint host (the Authorization header is delivered to whatever listens there), and presign() emits a URL carrying the bogus port verbatim.
  • HTTP_PROXY / HTTPS_PROXY: every proxied fetch is sent to port 80/443 of the proxy host.
// listener on 127.0.0.1:80 observes: GET /bk/k HTTP/1.1 (with SigV4 Authorization header)
const s3 = new Bun.S3Client({ endpoint: "http://127.0.0.1:107688", accessKeyId: "a", secretAccessKey: "b", bucket: "bk" });
await s3.file("k").text();

// HTTP_PROXY=http://127.0.0.1:107688 bun app.js
// listener on 127.0.0.1:80 observes: GET http://127.0.0.1:9/t HTTP/1.1
await fetch("http://127.0.0.1:9/t");

curl errors on these ("Port number was not a decimal number between 0 and 65535"), and Node with NODE_USE_ENV_PROXY=1 throws ERR_PROXY_INVALID_CONFIG.

Fix

  • bun_url::URL::has_invalid_port(): true when the port text is non-empty and does not parse as a u16.
  • HTTPThread::connect rejects before dialing: InvalidPort for the request URL, InvalidProxyPort for the proxy. This is the single choke point for every TCP connect (direct, proxied, custom TLS contexts, per redirect hop), so it also covers env-proxy re-resolution after a cross-scheme redirect and the install/registry paths. The unix-socket path is unaffected.
  • S3 signing (S3Credentials::sign) rejects an endpoint host with an unparseable port with ERR_S3_INVALID_ENDPOINT. This covers endpoints from S3_ENDPOINT / AWS_ENDPOINT env vars and presign(), which never reaches the connect path.
  • The S3Client endpoint option now throws ERR_INVALID_ARG_VALUE ("endpoint port must be a number between 0 and 65535") at construction, matching how fetch(url, { proxy }) rejects invalid proxy URLs.

Fetch rejections get explicit messages: "Invalid port number in [proxy ]URL. Ports must be a number between 0 and 65535."

Tests

  • test/js/bun/s3/s3.test.ts: constructor and per-file endpoint options throw for all four port shapes, valid ports still construct and presign with the port intact, and S3_ENDPOINT env var makes presign() throw ERR_S3_INVALID_ENDPOINT instead of emitting the URL.
  • test/js/bun/http/proxy.test.ts: HTTP_PROXY/http_proxy/HTTPS_PROXY with each bad-port shape rejects the fetch with InvalidProxyPort before any connection is attempted.

All fail on the unfixed build (the proxy ones fail with ConnectionRefused from the silent port-80 dial; presign emits the URL) and pass with the fix. Also patched the four existing loopback-S3 fixtures in s3.test.ts to stop inheriting ambient HTTP_PROXY from the environment, which broke them in containers that set one.

bun install --registry http://host:99999/ already failed URL joining before this change; the connect-layer check adds depth there rather than changing behavior.


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

fails on main (without fix)
ASAN without fix: 9 failed, 286 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/http/proxy.test.ts "test/js/bun/s3/s3.test.ts"
bun test v1.4.0 (4b7f963a4)

test/js/bun/s3/s3.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
(skip) R2 > s3 > fetch > bucket in path > should download file via fetch GET
(skip) R2 > s3 > fetch > bucket in path > should download range
(skip) R2 > s3 > fetch > bucket in path > should check if a key exists or content-length
(skip) R2 > s3 > fetch > bucket in path > should check if a key does not exist
(skip) R2 > s3 > fetch > bucket in path > should be able to set content-type
(skip) R2 > s3 > fetch > bucket in path > should be able to upload large files
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download file via Bun.s3().text()
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range with 0 offset
(skip) R2 > s3 > Bun.S3Client >
... (truncated)

release without fix: 8 failed, 287 skipped
bun test v1.4.0-canary.1 (b58cd4685)

test/js/bun/s3/s3.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
(skip) R2 > s3 > fetch > bucket in path > should download file via fetch GET
(skip) R2 > s3 > fetch > bucket in path > should download range
(skip) R2 > s3 > fetch > bucket in path > should check if a key exists or content-length
(skip) R2 > s3 > fetch > bucket in path > should check if a key does not exist
(skip) R2 > s3 > fetch > bucket in path > should be able to set content-type
(skip) R2 > s3 > fetch > bucket in path > should be able to upload large files
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download file via Bun.s3().text()
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range with 0 offset
(skip) R2 > s3 > Bun.S3Client > bucket in path > should check if a key exists or content-length
(skip) R2 > s3 > Bun.S3Client > bucket in path > should check if a key does not exist
(skip) R2 > s3 > Bun.S3Client > 
... (truncated)
passes on PR (with fix)
ASAN with fix: 286 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/bun/http/proxy.test.ts "test/js/bun/s3/s3.test.ts"
bun test v1.4.0 (4b7f963a4)

test/js/bun/s3/s3.test.ts:
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
(skip) R2 > s3 > fetch > bucket in path > should download file via fetch GET
(skip) R2 > s3 > fetch > bucket in path > should download range
(skip) R2 > s3 > fetch > bucket in path > should check if a key exists or content-length
(skip) R2 > s3 > fetch > bucket in path > should check if a key does not exist
(skip) R2 > s3 > fetch > bucket in path > should be able to set content-type
(skip) R2 > s3 > fetch > bucket in path > should be able to upload large files
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download file via Bun.s3().text()
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range
(skip) R2 > s3 > Bun.S3Client > bucket in path > should download range with 0 offset
(skip) R2 > s3 > Bun.S3Client >
... (truncated)

release with fix: 287 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     4b7f963a43
  features     baseline

22 deps, 106 codegen, 1175 objects in 914ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] install /workspace/bun
bun install v1.4.0-canary.1 (b58cd4685)

Checked 124 installs across 170 packages (no changes) [9.00ms]
[2/1238] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (b58cd4685)

Checked 1 install across 2 packages (no changes) [4.00ms]
[3/1238] gen ErrorCode+*.h
[4/1238] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (b58cd4685)

Checked 129 installs across 147 packages (no changes) [11.00ms]
[5/1238] gen bindgenv2
[6/1238] fetch picohttpparser
[picohttpparser] up to date
[7/1238] fetch tinycc
[tinycc] up to date
[8/1237] fetch zlib
[zlib] up to date
[9/1237] gen .bind.ts → GeneratedBindings.cpp
[10/1237] fetch libjpeg-turbo
[libjpeg-turbo] up to date
[11/1237] subst deps/zlib/zconf.h
[12/1237] gen JSBuffer.lut.h
Generating /workspace/bun/build/release/codegen/JSBuffer.lut.h fr
... (truncated)
diff hotspot
src/http/HTTPThread.rs                    | 14 ++++++
 src/http/error.rs                         |  6 +++
 src/runtime/webcore/fetch/FetchTasklet.rs |  6 +++
 src/runtime/webcore/s3/credentials_jsc.rs | 14 ++++++
 src/s3_signing/credentials.rs             | 21 +++++++++
 src/url/lib.rs                            |  8 ++++
 test/js/bun/http/proxy.test.ts            | 44 ++++++++++++++++++
 test/js/bun/s3/s3.test.ts                 | 75 +++++++++++++++++++++++++++++--
 8 files changed, 184 insertions(+), 4 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                       reads  edits  tests
src/http/HTTPThread.rs                         1      1      0
src/http/error.rs                              1      2      0
src/runtime/webcore/fetch/FetchTasklet.rs      5      1      0
src/runtime/webcore/s3/credentials_jsc.rs      3      3      0
src/s3_signing/credentials.rs                  1      3      0
src/url/lib.rs                                 5      1      0
test/js/bun/http/proxy.test.ts                 0      0      0
test/js/bun/s3/s3.test.ts                      1      1      0

URL::get_port_auto() turns a port that fails u16 parsing (":107688",
":99999", ":9000abc") into the scheme default, so consumers that skip
the WHATWG parser silently sent traffic to port 80/443 of the host:

- S3Client endpoint: the SigV4-signed request went to port 80/443 of
  the endpoint host, and presign() emitted the bogus port verbatim
- HTTP_PROXY / HTTPS_PROXY: every proxied fetch went to port 80/443 of
  the proxy host

HTTPThread::connect now rejects these before dialing (InvalidPort /
InvalidProxyPort), S3 signing rejects them with ERR_S3_INVALID_ENDPOINT,
and the S3Client endpoint option throws ERR_INVALID_ARG_VALUE at
construction.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next review available in: 7 minutes

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

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d4e03b6c-c04b-4077-ab40-9468b9cd2b28

📥 Commits

Reviewing files that changed from the base of the PR and between 17d6843 and 4b7f963.

📒 Files selected for processing (8)
  • src/http/HTTPThread.rs
  • src/http/error.rs
  • src/runtime/webcore/fetch/FetchTasklet.rs
  • src/runtime/webcore/s3/credentials_jsc.rs
  • src/s3_signing/credentials.rs
  • src/url/lib.rs
  • test/js/bun/http/proxy.test.ts
  • test/js/bun/s3/s3.test.ts

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

@github-actions github-actions Bot added the claude label Aug 6, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No bugs found, but this changes behavior in the HTTP client's single connect chokepoint (HTTPThread::connect) and in S3 signing — every network request and every SigV4 sign goes through these paths. Worth a human look at the new validation guards and the custom host_has_invalid_port port-splitting in s3_signing/credentials.rs.

What was reviewed:

  • has_invalid_port() and the connect-path guards — placed after the unix-socket branch, before any get_port_auto() call; covers direct, proxy, and custom-TLS-context paths.
  • The credentials_jsc.rs early return — str.deref() is called and utf8: ZigStringSlice drops on return, matching the existing invalid-endpoint branch a few lines below.
  • New http::Error variants — name() and the FetchTasklet message match are updated; no other exhaustive matches on http::Error found.
Extended reasoning...

Overview

The PR closes a hole where a URL with an unparseable :port (out-of-u16-range or trailing garbage) silently fell back to the scheme-default port 80/443 via URL::get_port_auto(). It adds URL::has_invalid_port() and rejects at three layers: the HTTP client's HTTPThread::connect (request URL and proxy URL), S3 credentials JS-option parsing (constructor / per-file endpoint), and S3 SigV4 signing (host_has_invalid_port on the endpoint host, covering env-var endpoints and presign()). Two new http::Error variants get names and fetch-facing messages. Tests cover env-proxy, S3Client constructor, per-file endpoint, valid-port passthrough, and the S3_ENDPOINT env-var presign path.

Security risks

This is a security-hardening fix (prevents SigV4 Authorization headers from being delivered to whatever listens on port 80/443 of a mistyped endpoint host, and prevents proxied traffic from going to port 80/443 of the proxy host). No new attack surface is introduced. The change is fail-closed: a previously-silently-misdirected request now errors.

Level of scrutiny

Medium-high. The validation logic itself is small and readable, but the placement is in Bun's single TCP-connect chokepoint — every fetch, every S3 op, every bun install network task funnels through HTTPThread::connect. The guard is placed after the unix-socket early return (correct — unix sockets ignore ports) and before every get_port_auto() call, and I traced all three connect branches (default context, custom-SSL cache hit, custom-SSL cache miss) — all read the port after the guard. But given this file's blast radius, a maintainer should confirm the placement.

The host_has_invalid_port helper in s3_signing/credentials.rs is bespoke port-splitting: it handles [ipv6]:port vs host:port by looking for a leading [. At this call site host is self.endpoint up to the first /, which for JS-supplied endpoints came from URL::parse().host_with_path(), but for env-supplied endpoints the normalization path may differ. A bare unbracketed IPv6 literal would false-positive here (first : treated as port separator), though that's unlikely to have worked before either. Worth a maintainer glance.

Other factors

  • The credentials_jsc.rs early-return cleanup mirrors the existing !endpoint.is_empty() invalid-endpoint branch below it: str.deref() explicit, utf8 drops via ZigStringSlice's Drop.
  • I checked for other exhaustive matches on http::Error that would need updating — none found beyond the name() match (updated) and the FetchTasklet message match (updated, has a fallback arm).
  • Tests are well-structured (subprocess isolation for env vars, concurrent, drain all pipes, assert exit code last, exercise the four bad-port shapes plus a valid-port control). The four unrelated s3.test.ts fixtures now clear ambient HTTP_PROXY — a hermeticity fix, but it means those tests' behavior changes in environments that set one.
  • This is a behavior change: configs that previously "worked" by accident (endpoint on 80/443 with a garbage :port) will now error. That's correct and matches curl/Node, but it's the kind of change a human should sign off on.

@robobun

robobun commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Verified the IPv6 concern from the review:

  • Bracketed IPv6 endpoints are unaffected: http://[::1]:9000 constructs and presigns with [::1]:9000 intact on both the old and new build, and http://[::1]:99999 now errors instead of signing for port 443.
  • A bare unbracketed IPv6 endpoint (http://::1:9000) was never accepted through the JS option path (the empty parsed hostname already raised ERR_INVALID_ARG_VALUE before this change). Through the env-var path it previously dialed the scheme-default port with an empty hostname; it now fails the sign with ERR_S3_INVALID_ENDPOINT, which is the intended fail-closed direction.

@robobun

robobun commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:26 PM PT - Aug 5th, 2026

✅ @robobun, your commit 4b7f963a433c58dac2e8adb27d290bd450d4ec44 passed in Build #89376! 🎉


🧪   To try this PR locally:

bunx bun-pr 37003

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

bun-37003 --bun

Jarred-Sumner pushed a commit that referenced this pull request Aug 21, 2026
### Problem
- `new Bun.S3Client({ endpoint: "http://127.0.0.1:A#@127.0.0.1:B" })`
signs for `127.0.0.1:B` and sends `PUT /bkt/k`, `Authorization` and the
body there. `new URL(endpoint).host` is `127.0.0.1:A`. `http://A\@B`
behaves the same. A service that checks a tenant's endpoint with `new
URL()` sends signed requests to a host it did not approve.
- Cause: `credentials_jsc.rs:119` and `env_loader.rs:281` give the raw
string to `bun_url::URL::parse`, whose credential and host scanning stop
only at `/` and `?`, and store its `host_with_path()`. Dot segments and
`127.1` are sent as written for the same reason.

### Fix
- `URL::parse_s3_endpoint` (`src/url/lib.rs`) keeps the existing `parse`
host check, so the accepted set does not change. It then parses the
string with WTF::URL (`https://` prepended when there is no scheme) and
stores its scheme, host with port, and path. When WTF::URL rejects the
input, it stores what `parse` reads, as today. Both stores call it.
- WTF::URL ends the authority at `#`, `\` and the last `@`, resolves dot
segments, canonicalizes the host and never puts credentials in it. The
stored form is unchanged, so `guess_region`, inspect output and
path-prefix endpoints still work. The notes list the visible side
effects.
- Verified: `test/js/bun/s3/s3-list-encode-overflow.test.ts` (the
network-free presign tests), 8 of the 12 new cases fail on 1.4.0. Also
`test/js/bun/s3/`: 156 pass, 1 timeout (notes).

### Background
- `S3Credentials.endpoint` stores `host[:port][/prefix]` without a
scheme. `sign_request` (`src/s3_signing/credentials.rs:384`) signs the
part before the first `/` as the host and sends it as `Host`. The stored
bytes are both signed for and connected to.
- Bun has two URL parsers. WTF::URL (WebKit) is the WHATWG parser behind
`new URL()`. `bun_url::URL::parse` slices an href that is already
normalized. `URL::from_string` and `OwnedURL` bridge the two.
- #38043 changes `URL::parse` itself for `user@host:port` and keeps `#`
in the authority on purpose. The two changes are independent. #16183 is
the umbrella issue for that parser.

<details><summary>Notes</summary>

Repro on release 1.4.0 with two `Bun.serve` listeners A and B on
127.0.0.1 (run without `HTTP_PROXY` set, since S3 ignores `NO_PROXY`,
#32045):

```
endpoint http://127.0.0.1:A#@127.0.0.1:B    new URL().host = 127.0.0.1:A
  A: (nothing)
  B: PUT /bkt/k host=127.0.0.1:B x-amz-security-token=SESSIONTOKENEXAMPLE
endpoint http://127.0.0.1:A\@127.0.0.1:B    same
```

presign() on 1.4.0 for the spellings in the test:

```
http://127.0.0.1:A#@127.0.0.1:B   -> http://127.0.0.1:B/bkt/k?...
http://127.0.0.1:A\@127.0.0.1:B   -> http://127.0.0.1:B/bkt/k?...
http://127.0.0.1:A?@127.0.0.1:B   -> http://A/bkt/k?...           (the port text becomes the host)
http://u:p@ss@127.0.0.1:A         -> http://ss@127.0.0.1:A/bkt/k?...   (a request resolves ss@127.0.0.1)
http://127.0.0.1:A/x/../prefix/   -> http://127.0.0.1:A/x/../prefix/bkt/k?...
http://127.1:A                    -> http://127.1:A/bkt/k?...
```

With the change every one of them starts with `http://127.0.0.1:A/`.
`\@` becomes the path prefix `/@127.0.0.1:B/`, which is what `new URL()`
reports for it. `S3_ENDPOINT` and `AWS_ENDPOINT` (`env_loader.rs`) had
the same problem and get the same treatment; the test covers
`S3_ENDPOINT` in a child process.

The test compares the presigned URL as text. `new URL(presigned)` would
itself strip a leaked `ss@`, resolve `/x/../` and canonicalize `127.1`,
and so hide the credential, dot-segment and `127.1` cases.

Why the `parse` host check is kept: `s3.test.ts` expects `endpoint:
"🙂.🥯"` and `"..asd.@%&&&%%"` to throw `ERR_INVALID_ARG_TYPE`, and
WTF::URL accepts the first as an IDN host. With the old check in front,
this change only alters what is stored for endpoints that were already
accepted.

Visible side effects besides the fix: a default port in the endpoint
(`http://h:80`) is now left out of the `Host` header, and a mixed-case
host is lowercased. Both come from the WTF::URL serialization.

Why the raw fallback: WTF::URL rejects a port such as `:99999`, which
today is accepted and connects to 80/443. #37003 turns that into an
error. This change leaves it alone so the two do not overlap.

Also unchanged: `guess_bucket`, the scheme-less R2 and
`s3.us-west-1.amazonaws.com` endpoints in `s3.test.ts` (stored byte for
byte as before), and a Supabase style `https://host/storage/v1/s3`
prefix (covered by the new test).

The S3 suite was run with the proxy variables of this environment unset.
The one timeout is `s3-list-objects.test.ts` "Should fall back to
NoSuchKey ...". Its `describe.concurrent` block runs it next to "Should
work with big responses", which takes about 18 s in this debug ASAN
build, so it exceeds its 5 s budget. It passes when run alone with this
build, and the stored endpoint for `server.url.href` is byte-identical
with and without the change.

`bun_s3_signing` does not depend on `bun_url`, and `bun_dotenv` must not
name `bun_s3_signing` types, so the helper lives in `bun_url` next to
`host_with_path()`, whose only callers were these two stores.

An earlier revision normalized the string with WTF::URL and sliced the
resulting href with `URL::parse` again. Review caught that this
inherited the `user@host:port` reading of `URL::parse` (#16181):
WTF::URL serializes `http://user:@h:1` as `http://user@h:1/`, so that
endpoint would have stored `user@h:1` where it stores `h:1` today. The
helper now reads scheme, host with port and path from the WTF::URL
components directly, and the `user:@` and `user@` spellings are in the
test. The second one is wrong on 1.4.0 as well.
</details>

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

---

**[review]** gate passed · iteration 0 · 4 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 8 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/s3/s3-list-encode-overflow.test.ts"
bun test v1.4.0 (6e906e4)

test/js/bun/s3/s3-list-encode-overflow.test.ts:
(pass) S3Client.list() option encoding > should not panic when prefix is longer than 1024 bytes when encoded [25.53ms]
(pass) S3Client.list() option encoding > should not panic when delimiter is longer than 1024 bytes when encoded [2.12ms]
(pass) S3Client.list() option encoding > should not panic when continuationToken is longer than 1024 bytes when encoded [1.78ms]
(pass) S3Client.list() option encoding > should not panic when startAfter is longer than 1024 bytes when encoded [2.24ms]
(pass) S3 object keys containing '?' or '#' > includes the full object key in the presigned URL path [9.33ms]
(pass) S3Client region option > rejects the region us-east-1/other.example.com because it is not a valid host name component [5.83ms]
(pass) S3Client region option > rejects the region us-east-1?x because it is not a valid host name component [1.85ms]
(pass) S3Client region option > rejects the region us-east-1#x because i
... (truncated)

release without fix: 8 FAILED
bun test v1.4.0-canary.1 (58d38cf)

test/js/bun/s3/s3-list-encode-overflow.test.ts:
(pass) S3Client.list() option encoding > should not panic when prefix is longer than 1024 bytes when encoded [0.15ms]
(pass) S3Client.list() option encoding > should not panic when delimiter is longer than 1024 bytes when encoded [0.03ms]
(pass) S3Client.list() option encoding > should not panic when continuationToken is longer than 1024 bytes when encoded [0.02ms]
(pass) S3Client.list() option encoding > should not panic when startAfter is longer than 1024 bytes when encoded [0.01ms]
(pass) S3 object keys containing '?' or '#' > includes the full object key in the presigned URL path [0.14ms]
(pass) S3Client region option > rejects the region us-east-1/other.example.com because it is not a valid host name component [0.07ms]
(pass) S3Client region option > rejects the region us-east-1?x because it is not a valid host name component [0.01ms]
(pass) S3Client region option > rejects the region us-east-1#x because it is not a valid host name component
(pass) S3Client region option > rejects the region us east 1 because it is not a valid host name component
(pass) S3Client region option 
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" "test/js/bun/s3/s3-list-encode-overflow.test.ts"
bun test v1.4.0 (6e906e4)

test/js/bun/s3/s3-list-encode-overflow.test.ts:
(pass) S3Client.list() option encoding > should not panic when prefix is longer than 1024 bytes when encoded [10.88ms]
(pass) S3Client.list() option encoding > should not panic when delimiter is longer than 1024 bytes when encoded [28.39ms]
(pass) S3Client.list() option encoding > should not panic when continuationToken is longer than 1024 bytes when encoded [3.34ms]
(pass) S3Client.list() option encoding > should not panic when startAfter is longer than 1024 bytes when encoded [2.50ms]
(pass) S3 object keys containing '?' or '#' > includes the full object key in the presigned URL path [14.80ms]
(pass) S3Client region option > rejects the region us-east-1/other.example.com because it is not a valid host name component [8.69ms]
(pass) S3Client region option > rejects the region us-east-1?x because it is not a valid host name component [2.69ms]
(pass) S3Client region option > rejects the region us-east-1#x because
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 770ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/6] gen generated_host_exports.rs
generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited
[1/6] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)

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

�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_base64 v0.0.0 (/workspace/bun/src/base64)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_paths v0.0.0
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/dotenv/env_loader.rs                       |  8 ++--
 src/runtime/webcore/s3/credentials_jsc.rs      |  9 ++--
 src/url/lib.rs                                 | 62 +++++++++++++++++++++++++-
 test/js/bun/s3/s3-list-encode-overflow.test.ts | 57 +++++++++++++++++++++++
 4 files changed, 125 insertions(+), 11 deletions(-)
```

</details>

**gate history** · 1 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                            reads  edits  tests
src/dotenv/env_loader.rs                            3      2      0
src/runtime/webcore/s3/credentials_jsc.rs           2      2      0
src/url/lib.rs                                      6     11      0
test/js/bun/s3/s3-list-encode-overflow.test.ts      2      4      0
```

</details>

<!-- robobun:evidence:end -->

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants