Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ba2b5cd
Merge branch 'maizatskyi/2026-05-08-ci' into 'gitlab'
mikea May 8, 2026
d4422a1
Merge branch 'maizatskyi/2026-05-11-ew-patches' into 'gitlab'
mikea May 11, 2026
2f10b22
Merge branch 'maizatskyi/2026-05-11-harris-pr' into 'gitlab'
mikea May 11, 2026
a6964d8
Merge branch 'aschwab/dlapid/predictableTests' into 'gitlab'
mikea May 11, 2026
c9af61c
Merge branch 'mar/vuln/ew/12' into 'gitlab'
mar-cf May 11, 2026
6709f84
Merge branch 'fix/autovuln-cloudflare-workerd-256' into 'gitlab'
mikea May 11, 2026
81cbe1e
Merge branch 'zak/mtls-fields' into 'gitlab'
fhanau May 11, 2026
179cbdb
Merge branch 'gbedford/zlib-compression-stream-gc-tracing' into 'gitlab'
guybedford May 12, 2026
9f35611
Merge branch 'ketan/fix-internal-build-retry' into 'gitlab'
ketanhwr May 12, 2026
716a564
Merge branch 'jasnell/streams-updates-jsvalues' into 'gitlab'
dcarney-cf May 12, 2026
708ec98
Merge branch 'arobinson/do-stub-memory-accounting' into 'gitlab'
a-robinson May 12, 2026
e957170
Merge branch 'mar/traceflags' into 'gitlab'
mar-cf May 12, 2026
fa6c4a7
Merge branch 'mar/context-hibernation-2' into 'gitlab'
mar-cf May 12, 2026
785a81b
Merge branch 'fix/autovuln-cloudflare-workerd-21' into 'gitlab'
ryanking13 May 13, 2026
5e62780
Merge branch 'fix/autovuln-cloudflare-workerd-198' into 'gitlab'
ryanking13 May 13, 2026
677a47c
Merge branch 'fix/autovuln-cloudflare-workerd-143' into 'gitlab'
dom96 May 13, 2026
f458031
Merge branch 'fix/autovuln-cloudflare-workerd-15' into 'gitlab'
dom96 May 13, 2026
223d89c
Merge branch 'fix/autovuln-cloudflare-workerd-156' into 'gitlab'
dom96 May 13, 2026
a21d9e0
Merge branch 'fix/autovuln-cloudflare-workerd-110' into 'gitlab'
git-bruh May 13, 2026
9aea9d3
Merge branch 'felix/051326-fix-ci' into 'gitlab'
fhanau May 13, 2026
a9221f3
Merge branch 'fix/autovuln-cloudflare-workerd-9' into 'gitlab'
mikea May 13, 2026
607c28e
Merge branch 'gbedford/jsg-visit-gc-lint' into 'gitlab'
guybedford May 13, 2026
a2068ac
Merge branch 'felix/revert-pr73' into 'gitlab'
guybedford May 14, 2026
776d93f
Merge branch 'fix/autovuln-cloudflare-workerd-30' into 'gitlab'
ryanking13 May 14, 2026
fdcef72
Merge branch 'fix/autovuln-cloudflare-workerd-95' into 'gitlab'
ryanking13 May 14, 2026
0ff1233
Merge branch 'fix/autovuln-cloudflare-workerd-334' into 'gitlab'
sohpeach May 14, 2026
2e9c451
Merge branch 'fix/autovuln-ew-edgeworker-13' into 'gitlab'
jp4a50 May 14, 2026
971573d
Merge branch 'fix/autovuln-cloudflare-workerd-16' into 'gitlab'
dom96 May 14, 2026
f237a08
Merge branch 'fix/autovuln-cloudflare-workerd-70' into 'gitlab'
ketanhwr May 14, 2026
25e51d1
Merge branch 'mar/vuln/14' into 'gitlab'
mar-cf May 14, 2026
d520587
Merge branch 'fix/autovuln-cloudflare-workerd-17' into 'gitlab'
ketanhwr May 14, 2026
c0a78fc
Merge branch 'fix/autovuln-cloudflare-workerd-18' into 'gitlab'
dom96 May 14, 2026
3ff2a3b
Merge branch 'fix/autovuln-cloudflare-workerd-44' into 'gitlab'
mikea May 14, 2026
fd99fcc
Merge branch 'maizatskyi/2026-05-14-fix-protobuf-warning' into 'gitlab'
mikea May 14, 2026
dc64cad
Mostly revert "Try to debug "pushed external is not a byte stream"."
kentonv May 1, 2026
56d03f3
Delete StreamSink in favor of ExternalPusher.
kentonv Apr 19, 2026
6f397d1
Remove ServerTopLevelMembrane, clean up autogate.
kentonv May 4, 2026
1441892
Allow channel tokens to be created asynchronously.
kentonv Apr 19, 2026
72f4a27
Resolve channels in `props` upfront.
kentonv May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ build --per_file_copt='external/zlib@-Wno-deprecated-non-prototype'
build --host_per_file_copt='external/zlib@-Wno-deprecated-non-prototype'
build --per_file_copt=external/protobuf@-Wno-deprecated-declarations
build --host_per_file_copt=external/protobuf@-Wno-deprecated-declarations
build --per_file_copt=external/protobuf@-Wno-deprecated-this-capture
build --host_per_file_copt=external/protobuf@-Wno-deprecated-this-capture

# opt in to capnp deprecation warnings about trying to attach to a refcounted object
build --cxxopt=-DKJ_WARN_REFCOUNTED_ATTACH=1
Expand Down
3 changes: 2 additions & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ Checks: >
-readability-redundant-smartptr-get,
readability-reference-to-constructed-temporary,
readability-static-accessed-through-instance,
readability-use-concise-preprocessor-directives
readability-use-concise-preprocessor-directives,
jsg-visit-for-gc
# TODO: Fix and enable
# bugprone-derived-method-shadowing-base-method
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
lint:
uses: ./.github/workflows/_bazel.yml
with:
extra_bazel_args: '--config=lint --config=clang-tidy --config=ci-test --config=ci-linux-common'
extra_bazel_args: '--config=lint --config=ci-test --config=ci-linux-common'
run_tests: false
parse_headers: true
secrets:
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ C++ classes are exposed to JavaScript via JSG macros in `src/workerd/jsg/`. See

- `JSG_RESOURCE_TYPE` for reference types, `JSG_STRUCT` for value types
- `js.alloc<T>()` for resource allocation
- The `jsg-visit-for-gc` clang-tidy check (`//tools/clang-tidy:jsg-lint`)
validates that GC-visitable fields are traced in `visitForGc()`. Run via
`just clang-tidy <target>`. See `build/AGENTS.md` for details.

### Feature Management

Expand Down
28 changes: 28 additions & 0 deletions build/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Custom Bazel rules (`wd_*` macros) for C++, TypeScript, Rust, Cap'n Proto, and t
| `wd_capnp_library.bzl` | Cap'n Proto schema compilation |
| `wd_rust_crate.bzl` / `wd_rust_binary.bzl` | Rust build rules |
| `lint_test.bzl` | ESLint integration |
| `//tools/clang-tidy:jsg-lint` | Custom clang-tidy plugin (source: `tools/clang-tidy/jsg-lint.c++`); ships the `jsg-visit-for-gc` check for GC-root validation |

**Conventions:**

Expand All @@ -28,6 +29,33 @@ Custom Bazel rules (`wd_*` macros) for C++, TypeScript, Rust, Cap'n Proto, and t
- Variant generation controllable per-test via `generate_*_variant` booleans
- `BUILD.*` files: overlay build files for third-party deps (sqlite3, zlib, simdutf, pyodide, wpt)

## CLANG-TIDY PLUGIN

`//tools/clang-tidy:jsg-lint` builds a shared-object clang-tidy plugin
that adds workerd-specific static checks. Currently ships `jsg-visit-for-gc`,
which flags JSG resource types whose visitable fields (`jsg::Ref`, `jsg::JsRef`,
`jsg::V8Ref`, `jsg::Function`, `jsg::Promise`, `jsg::BufferSource`, `jsg::Value`,
etc., plus `kj::Maybe`/`Array`/`Vector`/`OneOf` and `jsg::Optional` wrappers
thereof) are missing from `visitForGc()`.

- Run via `just clang-tidy <target>` (e.g., `just clang-tidy //src/workerd/api/...`).
- Plugin sources live in `tools/clang-tidy/jsg-lint.c++` and are built as a
`cc_shared_library` target `//tools/clang-tidy:jsg-lint`. The source is
also exported via `exports_files` so downstream projects can rebuild
against their own clang/LLVM headers.
- The clang-tidy binary itself is published to `cloudflare/workerd-tools`
releases (see `deps/build_deps.jsonc`, entries `clang_tidy_*`); the matching
`*_dev.tar.xz` archive provides the clang/LLVM headers needed to build the
plugin out-of-tree. Available for Linux amd64/arm64 and macOS arm64; a
single archive (linux-amd64) serves all platforms since the AST-matching
plugin doesn't depend on the arch-specific config macros that vary.
- Wrapper script `build/tools/clang_tidy/clang_tidy_wrapper.sh` loads the
plugin via `--load=`.
- Suppress an intentional non-visit with `// NOLINT(jsg-visit-for-gc)` plus a
comment explaining why the field is safe to skip (see `src/workerd/api/streams/queue.h`
for `ByteQueue::Entry::store` and `src/workerd/api/node/diagnostics-channel.h`
for `Channel::name`).

## DEPENDENCY MANAGEMENT

Lives in `deps/`. Uses jsonc manifests + codegen:
Expand Down
26 changes: 20 additions & 6 deletions build/deps/build_deps.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,41 @@
"type": "github_release",
"owner": "cloudflare",
"repo": "workerd-tools",
"file_regex": "llvm-.*-linux-amd64-clang-tidy",
"file_regex": "llvm-.*-linux-amd64-clang-tidy$",
"file_type": "executable",
"freeze_version": "clang-tidy-22.1.1"
"freeze_version": "clang-tidy-22.1.5"
},
{
"name": "clang_tidy_linux_arm64",
"type": "github_release",
"owner": "cloudflare",
"repo": "workerd-tools",
"file_regex": "llvm-.*-linux-arm64-clang-tidy",
"file_regex": "llvm-.*-linux-arm64-clang-tidy$",
"file_type": "executable",
"freeze_version": "clang-tidy-22.1.1"
"freeze_version": "clang-tidy-22.1.5"
},
{
"name": "clang_tidy_darwin_arm64",
"type": "github_release",
"owner": "cloudflare",
"repo": "workerd-tools",
"file_regex": "llvm-.*-darwin-arm64-clang-tidy",
"file_regex": "llvm-.*-darwin-arm64-clang-tidy$",
"file_type": "executable",
"freeze_version": "clang-tidy-22.1.1"
"freeze_version": "clang-tidy-22.1.5"
},
{
// Clang/LLVM headers needed to build the workerd jsg-lint clang-tidy
// plugin out-of-tree. The clang-tidy plugin is just AST matching, so
// the only platform-specific bits (arch-name macros in
// build/include/llvm/Config/*.def and llvm-config.h) are irrelevant
// here. One archive serves all platforms.
"name": "clang_tidy_dev_headers",
"type": "github_release",
"owner": "cloudflare",
"repo": "workerd-tools",
"file_regex": "llvm-.*-linux-amd64-clang-tidy-dev\\.tar\\.xz$",
"build_file": "@workerd//tools/clang-tidy:BUILD.headers",
"freeze_version": "clang-tidy-22.1.5"
}
]
}
29 changes: 20 additions & 9 deletions build/deps/gen/build_deps.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ bazel_dep(name = "abseil-cpp", version = "20260107.1")
bazel_dep(name = "apple_support", version = "2.5.4")

# aspect_rules_esbuild
bazel_dep(name = "aspect_rules_esbuild", version = "0.25.1")
bazel_dep(name = "aspect_rules_esbuild", version = "0.26.0")

# aspect_rules_js
bazel_dep(name = "aspect_rules_js", version = "3.0.3")
bazel_dep(name = "aspect_rules_js", version = "3.1.1")

# aspect_rules_ts
bazel_dep(name = "aspect_rules_ts", version = "3.8.9")
Expand All @@ -29,26 +29,37 @@ bazel_dep(name = "bazel_skylib", version = "1.9.0")
http.file(
name = "clang_tidy_darwin_arm64",
executable = True,
sha256 = "65599ed9056d5da503cd4a0b179276d0676d959eb3a6b19c1720fe4ac697891a",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-darwin-arm64-clang-tidy",
sha256 = "c499cb9cbcb3af9e7bce2da5d42fe3bfa957928620c73a435f5918e00cf08d6a",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.5/llvm-22.1.5-darwin-arm64-clang-tidy",
)
use_repo(http, "clang_tidy_darwin_arm64")

# clang_tidy_dev_headers
http.archive(
name = "clang_tidy_dev_headers",
build_file = "@workerd//tools/clang-tidy:BUILD.headers",
sha256 = "8c8f3e5abd3e48d2570bdbba9de6a6aa96f01c489ad4ccddeb0449b3a857d706",
strip_prefix = "llvm-22.1.5-linux-amd64-clang-tidy-dev",
type = "tar.xz",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.5/llvm-22.1.5-linux-amd64-clang-tidy-dev.tar.xz",
)
use_repo(http, "clang_tidy_dev_headers")

# clang_tidy_linux_amd64
http.file(
name = "clang_tidy_linux_amd64",
executable = True,
sha256 = "52b56c8f46a80dbbde9334f3de0da45744e65757b1ab467e513d5d8cd1d0b771",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-linux-amd64-clang-tidy",
sha256 = "ef023eeeafba064d4f182ce130b306202a21f23632ad4253687ed10b7df493da",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.5/llvm-22.1.5-linux-amd64-clang-tidy",
)
use_repo(http, "clang_tidy_linux_amd64")

# clang_tidy_linux_arm64
http.file(
name = "clang_tidy_linux_arm64",
executable = True,
sha256 = "1ac2e03fee590aaf920861e83be27e8936cd9a5476a90f43bf88c8e6eb424be6",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.1/llvm-22.1.1-linux-arm64-clang-tidy",
sha256 = "59a52ec78d370141667022fe33dd12b17568f3c1a466641c5df52790a570556d",
url = "https://github.com/cloudflare/workerd-tools/releases/download/clang-tidy-22.1.5/llvm-22.1.5-linux-arm64-clang-tidy",
)
use_repo(http, "clang_tidy_linux_arm64")

Expand All @@ -68,7 +79,7 @@ bazel_dep(name = "rules_nodejs", version = "6.7.4")
bazel_dep(name = "rules_oci", version = "2.3.0")

# rules_python
bazel_dep(name = "rules_python", version = "2.0.0")
bazel_dep(name = "rules_python", version = "2.0.1")

# rules_rust
bazel_dep(name = "rules_rust", version = "0.70.0")
Expand Down
12 changes: 6 additions & 6 deletions build/deps/gen/deps.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ bazel_dep(name = "brotli", version = "1.2.0.bcr.1")
# capnp-cpp
http.archive(
name = "capnp-cpp",
sha256 = "16bc50865c40448f16b10bea21098e12a175cd1046aa1518e04b9e4f4ad13670",
strip_prefix = "capnproto-capnproto-23bb5b9/c++",
sha256 = "a87651d1772c138643e1fc28ca0cbc8eda0445ca265bc200c083c31a75919386",
strip_prefix = "capnproto-capnproto-911e53d/c++",
type = "tgz",
url = "https://github.com/capnproto/capnproto/tarball/23bb5b957cb1ab9f92734341b5664150e71890f2",
url = "https://github.com/capnproto/capnproto/tarball/911e53d67841687afe9a349fd8c0d39fe024515a",
)
use_repo(http, "capnp-cpp")

Expand Down Expand Up @@ -136,10 +136,10 @@ bazel_dep(name = "tcmalloc", version = "0.0.0-20250927-12f2552")
# workerd-cxx
http.archive(
name = "workerd-cxx",
sha256 = "fbba1b102b2c4fe879b2f610d7e94ceda6beceac3d57a27196482ce3e9536b50",
strip_prefix = "cloudflare-workerd-cxx-c677ef5",
sha256 = "31052a6fec0da501196a4f026469b837ef688c49b455fc437cdb70281f6b38cb",
strip_prefix = "cloudflare-workerd-cxx-a53da2e",
type = "tgz",
url = "https://github.com/cloudflare/workerd-cxx/tarball/c677ef53092a8425ce9f059074441fdb1b7c1ed3",
url = "https://github.com/cloudflare/workerd-cxx/tarball/a53da2e9d35710dcad089574625b6c01cf9535d3",
)
use_repo(http, "workerd-cxx")

Expand Down
2 changes: 2 additions & 0 deletions build/deps/update-deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ def gen_github_release(repo):
type = "tgz"
if url.endswith(".zip"):
type = "zip"
elif url.endswith(".tar.xz") or url.endswith(".txz"):
type = "tar.xz"
elif url.endswith(".xz"):
type = "xz"
elif url.endswith(".tar.bz2"):
Expand Down
1 change: 1 addition & 0 deletions build/tools/clang_tidy/clang_tidy.bazelrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# enable clang tidy checks with default configuration
build:lint --config=clang-tidy
build:clang-tidy --aspects //build/tools/clang_tidy:clang_tidy.bzl%clang_tidy_aspect --output_groups=+clang_tidy_checks
build:clang-tidy-only --aspects //build/tools/clang_tidy:clang_tidy.bzl%clang_tidy_aspect --output_groups=clang_tidy_checks

Expand Down
8 changes: 8 additions & 0 deletions build/tools/clang_tidy/clang_tidy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,11 @@ def _clang_tidy_aspect_impl(target, ctx):
ctx.attr._clang_tidy_executable.files,
ctx.attr._clang_tidy_wrapper.files,
ctx.attr._clang_tidy_config.files,
ctx.attr._clang_tidy_plugin.files,
]

plugin_path = ctx.attr._clang_tidy_plugin.files.to_list()[0].path

outs = []
for src in srcs:
# run actions need to produce something, declare a dummy file
Expand All @@ -112,6 +115,7 @@ def _clang_tidy_aspect_impl(target, ctx):
# these are consumed by clang_tidy_wrapper,sh
args.add(ctx.attr._clang_tidy_executable.files_to_run.executable)
args.add(out)
args.add(plugin_path)

# clang-tidy arguments
# do not print statistics
Expand Down Expand Up @@ -195,6 +199,10 @@ clang_tidy_aspect = aspect(
default = Label("//:clang_tidy_config"),
allow_single_file = True,
),
"_clang_tidy_plugin": attr.label(
default = Label("//tools/clang-tidy:jsg-lint"),
allow_single_file = True,
),
"_clang_tidy_compiler_flags": attr.string_list(
default = [],
),
Expand Down
6 changes: 5 additions & 1 deletion build/tools/clang_tidy/clang_tidy_wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ shift
OUTPUT=$1
shift

# Path to the workerd jsg-lint plugin shared library.
CLANG_TIDY_PLUGIN=$1
shift

PWD=$(pwd)/
ESCAPED_PWD=$(sed 's/[\*\.&/]/\\&/g' <<< "$PWD")

Expand All @@ -18,7 +22,7 @@ ESCAPED_PWD=$(sed 's/[\*\.&/]/\\&/g' <<< "$PWD")
CLANG_TIDY_STDERR=$(mktemp)

set +e
"${CLANG_TIDY_BIN}" "$@" 2>"$CLANG_TIDY_STDERR" | \
"${CLANG_TIDY_BIN}" "--load=${CLANG_TIDY_PLUGIN}" "$@" 2>"$CLANG_TIDY_STDERR" | \
# clang-tidy insists on printing absolute file paths, chop current dir off
sed "s/$ESCAPED_PWD//g"
CLANG_TIDY_EXIT_CODE=$?
Expand Down
3 changes: 3 additions & 0 deletions build/wd_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def wd_test(
generate_all_autogates_variant = True,
generate_all_compat_flags_variant = True,
generate_gc_stress_variant = True,
predictable = True,
compat_date = "",
**kwargs):
"""Rule to define tests that run `workerd test` with a particular config.
Expand All @@ -28,6 +29,7 @@ def wd_test(
generate_all_autogates_variant: If True (default), generate @all-autogates variants.
generate_all_compat_flags_variant: If True (default), generate @all-compat-flags variants.
generate_gc_stress_variant: If True (default), generate @gc-stress variant.
predictable: If True (default), pass `--predictable` to workerd.
compat_date: If specified, use this compat date for the default variant instead of 2000-01-01.
Does not affect the @all-compat-flags variant which always uses 2999-12-31.

Expand Down Expand Up @@ -87,6 +89,7 @@ def wd_test(
base_args = [
"$(location //src/workerd/server:workerd_cross)",
"test",
] + (["--predictable"] if predictable else []) + [
"$(location {})".format(src),
] + args

Expand Down
4 changes: 4 additions & 0 deletions build/wpt_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ def wpt_test(name, wpt_directory, config, compat_date = "", compat_flags = [], a
sidecar = "@wpt//:entrypoint" if start_server else None,
compat_date = compat_date,
generate_all_compat_flags_variant = False, # Already using future date where possible.
# Predictable mode enables V8's --expose-gc, which can trigger additional JIT events
# that produce INFO log lines on stdout. Those lines land after the JSON report/stats
# blobs emitted by the WPT harness and break tools/cross/wpt_logs.py parsing.
predictable = False,
data = data,
**kwargs
)
Expand Down
4 changes: 3 additions & 1 deletion src/node/internal/crypto_keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,9 @@ export function createSecretKey(
key = Buffer.from(new Uint8Array(key));
} else if (isArrayBufferView(key)) {
// We want the key to be a copy of the original buffer, not a view.
key = Buffer.from(key as Buffer);
key = Buffer.from(
new Uint8Array(key.buffer, key.byteOffset, key.byteLength)
);
}

// Node.js requires that the key data be less than 2 ** 32 - 1,
Expand Down
36 changes: 34 additions & 2 deletions src/node/internal/internal_http_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ import type { Socket } from 'node:net';

const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;

// Matches paths that would override the URL authority when passed to
// `new URL(path, base)`: double separators (// /\ \/ \\) or a scheme
// (colon before the first separator).
const AUTHORITY_OVERRIDE_REGEX = /^(?:[/\\]{2}|[^/\\]*:)/;

type WriteCallback = (err?: Error) => void;

function validateHost(host: unknown, name: string): string {
Expand Down Expand Up @@ -116,6 +121,22 @@ export class ClientRequest extends OutgoingMessage implements _ClientRequest {
if (INVALID_PATH_REGEX.test(options.path)) {
throw new ERR_UNESCAPED_CHARACTERS('Request path');
}
// Reject paths that would override the URL authority when passed to
// `new URL(path, base)`. Two cases:
//
// 1. Network-path references and backslash variants — the WHATWG URL
// parser treats \ as / for special schemes, so any pair of / and \
// at the start (// /\ \/ \\) introduces an authority.
//
// 2. Absolute-form URLs — a scheme (e.g. "http:") before the first
// separator causes the parser to ignore the base entirely.
if (AUTHORITY_OVERRIDE_REGEX.test(options.path)) {
throw new ERR_INVALID_ARG_VALUE(
'options.path',
options.path,
'must be a path-only request target'
);
}
}

type AgentLike = Agent | boolean | null | undefined;
Expand Down Expand Up @@ -353,12 +374,23 @@ export class ClientRequest extends OutgoingMessage implements _ClientRequest {
return;
}

const host = this.getHeader('host') ?? this.host;
let url = new URL(`http://${host}`);
let url = new URL(`http://${this.host}`);
url.protocol = this.protocol;
url.port = this.port;

if (this.path.length > 0 && this.path !== '/') {
// Defense-in-depth: re-validate in case this.path was mutated after
// construction (the field is public).
if (AUTHORITY_OVERRIDE_REGEX.test(this.path)) {
this.destroy(
new ERR_INVALID_ARG_VALUE(
'options.path',
this.path,
'must be a path-only request target'
)
);
return;
}
// We pass `path` as the first argument since it can contain search and hash components.
// Therefore, running the pathname setter will not work.
// Since this is an extremely costly operation, we only do it if necessary.
Expand Down
Loading
Loading