Skip to content

Conversation

caitp
Copy link
Contributor

@caitp caitp commented Apr 24, 2025

By default, Curl does not send a CONNECT request to a non-secure Proxy with a non-secure endpoint. This can be overridden using the --proxytunnel or -p parameter.

This change modifies ProxyAgent's constructor to accept a tunnelProxy option, sends a CONNECT if either tunnelProxy is true, or either the Proxy or endpoint use a non-'http:' protocol.

Disabling tunneling for HTTP->HTTP by default would be a breaking change, so currently, the tunneling behavior requires an opt-out. This may change depending on feedback during code review.

This adds a new test case which explicitly disables tunneling for an HTTP->HTTP connection, and asserts that no CONNECT message is sent to the server or proxy, and that the expected HTTP request is sent to the proxy.

Closes #4083

This relates to...

#4083

Rationale

Closer alignment with widely used industry standard libraries (specifically Curl)

Changes

  • A new tunnelProxy option is added to the ProxyAgent dispatcher which requests whether or not CONNECT requests be sent to an HTTP->HTTP Proxy
  • A hackish rawSocket option is added to Request. This is awkward, advice on a cleaner/better way to do this would be appreciated. This conveys to a dispatcher several layers down that it should avoid proceeding with a CONNECT request and instead fulfill the connect() call with just the socket.
  • A new test case showcasing the altered behavior when opts.tunnelProxy===false

Features

The new tunnelProxy option, which permits matching Curl (however, currently this sort of works in the opposite way to Curl, as tunneling occurs by default and requires opt-out rather than opt-in)

Bug Fixes

#4083

Breaking Changes and Deprecations

It would technically be a breaking change to fully match Curl by opting out of tunneling by default. That is (in the first iteration of this patch) not done.

Status

@mcollina
Copy link
Member

cc @joyeecheung

Copy link
Member

@metcoder95 metcoder95 left a comment

Choose a reason for hiding this comment

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

lgtm, just documentation left

@caitp
Copy link
Contributor Author

caitp commented May 15, 2025

I may have accidentaly messed up the merge, so replacing that with a force push

@metcoder95
Copy link
Member

tests does not seems happy

@caitp
Copy link
Contributor Author

caitp commented May 15, 2025

tests does not seems happy

seems to be from new stuff imported by dcf82a7, the same test fails with this whole PR reverted

caitp added 7 commits May 19, 2025 10:23
Curl does not send a CONNECT request for to a Proxy server, by default,
for cleartext communications to an endpoint, via a cleartext connection
to a Proxy. It permits forcing a CONNECT request to be sent via the
--tunnelproxy parameter.

This change modifies ProxyAgent's constructor to accept a `tunnelProxy`
option, sends a CONNECT if either `tunnelProxy` is true, or either the
Proxy or endpoint use a non-http: protocol.

Disabling tunneling for HTTP->HTTP by default would be a breaking change, so
currently, the tunneling behaviour requires an opt-out. This may change depending
on feedback during code review.

This adds a new test case which explicitly disables tunneling for an HTTP->HTTP
connection, and asserts that no CONNECT message is sent to the server or proxy,
and that the expected HTTP request is sent to the proxy.

Closes nodejs#4083
This version tries to expose less sketchiness -- it's not particularly well organized yet, and
I'm sure it could be cleaned up a lot.

Instead of adding the "rawSocket" stuff to RequestOptions, there's a new wrapper ProxyClient added,
which intercepts the CONNECT message and prevents it from being dispatched.

Unfortunately the wrapper client isn't quite written in a way to manage all of the client-ness,
so ProxyAgent is still responsible for updating the PATH of HTTP->HTTP Proxy requests to include
the endpoint domain.

It is messy though, admittedly.
initially just wanted to fix a typo, but thought maybe the original explanation wasn't great.
Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

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

lgtm

@mcollina mcollina merged commit 95fd9d3 into nodejs:main May 20, 2025
28 of 31 checks passed
@github-actions github-actions bot mentioned this pull request May 20, 2025
@joyeecheung
Copy link
Member

joyeecheung commented Jun 27, 2025

Was trying to update the test expectations in Node.js now that this gets fixed in nodejs/node#58859, but I see that it still gets a CONNECT for the HTTP test. I think the problem is that tunnelProxy: false is an agent-wide setting, but what we want is for it to be automatic - false for HTTP traffic, true for HTTPS, I think that's the default behavior of curl as well.

@metcoder95
Copy link
Member

cc: @caitp

@caitp
Copy link
Contributor Author

caitp commented Jun 30, 2025

I think this is right there in the PR description :P "Disabling tunneling for HTTP->HTTP by default would be a breaking change, so currently, the tunneling behavior requires an opt-out. This may change depending on feedback during code review."

There is already logic to tunnel if an endpoint or the proxy itself uses a secure protocol -- it should be the same logic as in Curl --- the only thing needed to change this to be the default is just flipping the default flag value to false, and then tunneling for unsecure protocols would become opt-in.

@metcoder95
Copy link
Member

If that's the case, we can keep this until further major where the flag is turned true by default

@caitp
Copy link
Contributor Author

caitp commented Jul 1, 2025

I should add, there do seem to be some issues with it, some of which have been raised by Joyee in the past, so I am working on an update to fix it up a bit -- maybe flipping the flag by default makes sense after that

caitp added a commit to caitp/undici that referenced this pull request Jul 16, 2025
…ons (nodejs#4180)

This refactors the way the legacy unsecured behaviour is implemented, by wrapping the Proxy client in a wrapper which rewrites requests, and handles errors. This will also insert authentication headers in each request.
caitp added a commit to caitp/undici that referenced this pull request Jul 23, 2025
…ons (nodejs#4180)

This refactors the way the legacy unsecured behaviour is implemented, by wrapping the Proxy client in a wrapper which rewrites requests, and handles errors. This will also insert authentication headers in each request.
mcollina pushed a commit that referenced this pull request Jul 24, 2025
…ons (#4180) (#4340)

* feat(ProxyAgent) improve Curl-y behavior in HTTP->HTTP Proxy connections (#4180)

This refactors the way the legacy unsecured behaviour is implemented, by wrapping the Proxy client in a wrapper which rewrites requests, and handles errors. This will also insert authentication headers in each request.

* add a test to attempt multiple concurrent connections with a single HttpContext

* be explicit about proxyTunnel status in each ProxyAgent test
Copy link
Contributor

The backport to v6.x failed:

The process '/usr/bin/git' failed with exit code 1

To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-v6.x v6.x
# Navigate to the new working tree
cd .worktrees/backport-v6.x
# Create a new branch
git switch --create backport-4180-to-v6.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 95fd9d3feebcdede11a4e6b66f57aac34abf482f
# Push it to GitHub
git push --set-upstream origin backport-4180-to-v6.x
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-v6.x

Then, create a pull request where the base branch is v6.x and the compare/head branch is backport-4180-to-v6.x.

metcoder95 pushed a commit that referenced this pull request Aug 21, 2025
…#4180)

* feat(ProxyAgent): match Curl behaviour for http-http Proxy connections

Curl does not send a CONNECT request for to a Proxy server, by default,
for cleartext communications to an endpoint, via a cleartext connection
to a Proxy. It permits forcing a CONNECT request to be sent via the
--tunnelproxy parameter.

This change modifies ProxyAgent's constructor to accept a `tunnelProxy`
option, sends a CONNECT if either `tunnelProxy` is true, or either the
Proxy or endpoint use a non-http: protocol.

Disabling tunneling for HTTP->HTTP by default would be a breaking change, so
currently, the tunneling behaviour requires an opt-out. This may change depending
on feedback during code review.

This adds a new test case which explicitly disables tunneling for an HTTP->HTTP
connection, and asserts that no CONNECT message is sent to the server or proxy,
and that the expected HTTP request is sent to the proxy.

Closes #4083

* Part 2

This version tries to expose less sketchiness -- it's not particularly well organized yet, and
I'm sure it could be cleaned up a lot.

Instead of adding the "rawSocket" stuff to RequestOptions, there's a new wrapper ProxyClient added,
which intercepts the CONNECT message and prevents it from being dispatched.

Unfortunately the wrapper client isn't quite written in a way to manage all of the client-ness,
so ProxyAgent is still responsible for updating the PATH of HTTP->HTTP Proxy requests to include
the endpoint domain.

It is messy though, admittedly.

* remove rawSocket from Dispatcher type definition

* Add some docs

* rename to proxyTunnel to match CURL

* Rename  to  in the docs, too

* Try to clarify the docs a bit

initially just wanted to fix a typo, but thought maybe the original explanation wasn't great.

(cherry picked from commit 95fd9d3)
Uzlopak pushed a commit that referenced this pull request Aug 21, 2025
…#4180) (#4433)

* feat(ProxyAgent): match Curl behavior in HTTP->HTTP Proxy connections (#4180)

* feat(ProxyAgent): match Curl behaviour for http-http Proxy connections

Curl does not send a CONNECT request for to a Proxy server, by default,
for cleartext communications to an endpoint, via a cleartext connection
to a Proxy. It permits forcing a CONNECT request to be sent via the
--tunnelproxy parameter.

This change modifies ProxyAgent's constructor to accept a `tunnelProxy`
option, sends a CONNECT if either `tunnelProxy` is true, or either the
Proxy or endpoint use a non-http: protocol.

Disabling tunneling for HTTP->HTTP by default would be a breaking change, so
currently, the tunneling behaviour requires an opt-out. This may change depending
on feedback during code review.

This adds a new test case which explicitly disables tunneling for an HTTP->HTTP
connection, and asserts that no CONNECT message is sent to the server or proxy,
and that the expected HTTP request is sent to the proxy.

Closes #4083

* Part 2

This version tries to expose less sketchiness -- it's not particularly well organized yet, and
I'm sure it could be cleaned up a lot.

Instead of adding the "rawSocket" stuff to RequestOptions, there's a new wrapper ProxyClient added,
which intercepts the CONNECT message and prevents it from being dispatched.

Unfortunately the wrapper client isn't quite written in a way to manage all of the client-ness,
so ProxyAgent is still responsible for updating the PATH of HTTP->HTTP Proxy requests to include
the endpoint domain.

It is messy though, admittedly.

* remove rawSocket from Dispatcher type definition

* Add some docs

* rename to proxyTunnel to match CURL

* Rename  to  in the docs, too

* Try to clarify the docs a bit

initially just wanted to fix a typo, but thought maybe the original explanation wasn't great.

(cherry picked from commit 95fd9d3)

* fix: test

---------

Co-authored-by: ⭐caitp⭐ <[email protected]>
metcoder95 pushed a commit that referenced this pull request Aug 21, 2025
…ons (#4180) (#4340)

* feat(ProxyAgent) improve Curl-y behavior in HTTP->HTTP Proxy connections (#4180)

This refactors the way the legacy unsecured behaviour is implemented, by wrapping the Proxy client in a wrapper which rewrites requests, and handles errors. This will also insert authentication headers in each request.

* add a test to attempt multiple concurrent connections with a single HttpContext

* be explicit about proxyTunnel status in each ProxyAgent test

(cherry picked from commit b7513d4)
Uzlopak pushed a commit that referenced this pull request Aug 22, 2025
…ons (#4180) (#4340) (#4445)

* feat(ProxyAgent) improve Curl-y behavior in HTTP->HTTP Proxy connections (#4180) (#4340)

* test: adjust v18 expectations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ProxyAgent has different behavior from curl -x

4 participants