fix(proxy): send SOCKS4 and caller-supplied URL fetches through the session proxy - #1629
Merged
Merged
Conversation
…ession proxy A Baileys session with a SOCKS4 proxy still fetched from the gateway's own address: the HTTP client had no SOCKS4 transport, so inbound media was skipped and arrived as the omitted marker, the WhatsApp Web version was not looked up, and the initial-sync payloads and a product card's image URL went direct. One undici connector over the socks package now covers socks4 and socks5, so a dispatcher exists for all four schemes and the skip branches those two fetches needed are gone. SOCKS4 has no hostname form, so the destination is resolved locally to IPv4; socks5 keeps the name on the wire for the proxy's resolver. Credentials on a socks4 URL cannot authenticate, which is now reported at session start. A media URL passed to a send route or to a media/convert route, and the link preview of a text send, were fetched outside the session's proxy on every scheme, because that fetch belongs to the request rather than to the session. The guarded fetch now takes the session proxy and uses it on every branch that opens a socket, including an IP literal with nothing to pin and the guard-off path; an unusable proxy value fails the request instead of falling back to a direct fetch. Through SOCKS the vetted addresses are what the proxy is asked to connect to, dialled in resolver order so a proxy that routes only one address family still reaches a dual-stack host, and a socks4 proxy is given an IPv4 one; through an HTTP proxy the destination is resolved by the proxy, so pinning is not expressible there. The proxy is the one the session's live engine was started with, since PATCH /proxy does not restart it, falling back to the stored row when nothing is running. SESSION_PROXY_URL_FETCH=false restores the direct fetch for a WhatsApp-only proxy. Fixes #1626
…-url-proxy # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two egress gaps on a proxied session. A Baileys session with a SOCKS4 proxy still fetched from the gateway's own address, because the HTTP client had no SOCKS4 transport: inbound media was skipped entirely and arrived as the omitted marker, the WhatsApp Web version was not looked up, and the history-sync payloads, app-state blobs and a product card's image URL went direct. On every scheme, a media URL passed to a send route or to
POST /api/sessions/{sessionId}/media/convert/voice|video, and the link preview of a text send, were fetched by the gateway outside the session's proxy, because that fetch belongs to the request rather than to the session.sockspackage (added as a direct dependency; it was already in the tree undersocks-proxy-agent) covers SOCKS4 and SOCKS5, socreateProxyDispatchernow returns a dispatcher for all four schemes. The SOCKS4 null path and undici's experimental SOCKS5 agent, with the percent-decoding workaround its credential handling needed, are gone, along with the skip branches they forced on the inbound media download and the version resolver.socksDestinationsrecords the choice and notes thatsocks-proxy-agent, which the session's WebSocket rides, resolvessocks5://locally too.socks4://proxy are reported at session start, the way the whatsapp-web.js engine already reports SOCKS credentials Chromium cannot use: SOCKS4 has no authentication step, so the user name travels as the connect request's user id and the password is dropped.withSafeFetchtakes the session's proxy and uses it on every branch that opens a socket: the pinned-hostname path, an IP literal with nothing to pin, each hop of a followed redirect chain, and the guard-off path. An unusable proxy value throws rather than falling back to a direct fetch; the caller sees the send or conversion fail, and a link preview is simply omitted.loadRemoteMediaBufferandgenerateSafeLinkPreviewtake the session proxy as a required argument through the adapters, so a send by URL on either engine, aurlconversion and every text-bearing send's preview (a plain send, a reply, an edit) all leave through it. The whatsapp-web.js path shares the same helper, so its five media call sites are covered too.CONNECTline and resolved by the proxy, so pinning is not expressible there; the scheme and blocked-address checks still run before any socket is opened. The unproxied path is unchanged: the same pinnedAgent, and no dispatcher for an IP literal.EngineRegistrybeside the engine, becausePATCH /proxyedits the row without restarting the engine and a fetch attributed to a running session must leave from the same address as the rest of its traffic. With no live engine there is no such egress, so the stored row is used, which keeps the fetch off the gateway's address for a session an operator has proxied. That row read happens before the conversion's URL error mapping, so a database failure stays a 500 instead of becoming a 400 carrying the driver's message.Behaviour change for operators: a caller-supplied URL now leaves through the session proxy.
SESSION_PROXY_URL_FETCH=falserestores the direct fetch for a proxy that only routes to WhatsApp and cannot reach arbitrary media hosts; the key is validated at boot like its siblings, forwarded by both compose files, and documented in.env.example,docs/06and the upgrade-hazards table.Verified with real in-process SOCKS4 and SOCKS5 servers that record what the client asked for and relay every tunnel to a local origin: an http destination end to end on both schemes, an https destination by the TLS ClientHello arriving on the tunnelled socket, the resolved IPv4 address (not merely its shape, which the SOCKS4a form would also satisfy) on the wire for SOCKS4 against the hostname for SOCKS5, the IPv4 entry of a dual-stack vetted list on SOCKS4, the fallback to the next vetted address against a SOCKS5 proxy that answers "host unreachable" for IPv6, the vetted address overriding the URL's own, SOCKS5 username/password authentication with percent-encoded credentials, and the SOCKS4 user id. The guard is exercised on all four branches plus the unproxied one, which must never touch the proxy. Every new behaviour was mutation-checked by reverting its line and confirming the guarding spec fails. The CI gate set was run locally.
The one
package-lock.jsonline outside thesocksentry ("dev": trueon@pkgjs/parseargs) is pre-existing drift thatnpm install --package-lock-onlyreconciles from the base commit with nopackage.jsonchange;@pkgjs/parseargsis an optional dependency ofjackspeakwith a working fallback, sonpm ci --omit=devis unaffected.Fixes #1626