Skip to content

fix(baileys): enforce correct SOCKS5 protocol usage in service integr…#2416

Open
wikirp wants to merge 1 commit intoEvolutionAPI:mainfrom
wikirp:fix/proxy-socks5-handshake
Open

fix(baileys): enforce correct SOCKS5 protocol usage in service integr…#2416
wikirp wants to merge 1 commit intoEvolutionAPI:mainfrom
wikirp:fix/proxy-socks5-handshake

Conversation

@wikirp
Copy link

@wikirp wikirp commented Feb 6, 2026

📋 Description

This PR fixes a critical issue in whatsapp.baileys.service.ts regarding proxy configuration. It now verifies if a protocol is already defined in the proxy settings and respects it instead of defaulting to http.

Previously, the logic incorrectly forced an HTTP fallback even when socks5 was explicitly configured. This behavior caused connection failures with strict SOCKS5 proxies (such as Rust or Go-based implementations) because the client inadvertently sent HTTP CONNECT headers (0x67) instead of the required SOCKS handshake (0x05).

The fix enforces the use of socksDispatcher when a SOCKS proxy is detected or explicitly configured, ensuring the correct handshake protocol is established.

🔗 Related Issue

Closes #

🧪 Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)

  • ✨ New feature (non-breaking change which adds functionality)

  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)

  • 📚 Documentation update

  • 🔧 Refactoring (no functional changes)

  • ⚡ Performance improvement

  • 🧹 Code cleanup

  • 🔒 Security fix

🧪 Testing

  • Manual testing completed

  • Functionality verified in development environment

  • No breaking changes introduced

  • Tested with different connection types (Verified with strict SOCKS5 Residential Proxy)

Test Scenario:

  • Configured a WhatsApp instance using a strict SOCKS5 proxy (Rust implementation) without HTTP support.

  • Before fix: Connection failed immediately with "Protocol Incorrect" errors on the proxy server (Client sent CONNECT verb).

  • After fix: The client correctly initiated the SOCKS5 handshake, the QR code loaded successfully, and the instance reached "Connected" status.

📸 Screenshots (if applicable)

(You can drag and drop your "Connected" screenshot here to show proof of success)
✅ Checklist

  • My code follows the project's style guidelines

  • I have performed a self-review of my code

  • I have commented my code, particularly in hard-to-understand areas

  • I have made corresponding changes to the documentation

  • My changes generate no new warnings

  • I have manually tested my changes thoroughly

  • I have verified the changes work with different scenarios

  • Any dependent changes have been merged and published

📝 Additional Notes

This change is essential for users deploying Evolution API behind residential proxies or private tunneling infrastructures that enforce strict protocol compliance and do not support HTTP tunneling on SOCKS ports.

Summary by Sourcery

Bug Fixes:

  • Handle proxy list entries that already include a protocol and avoid forcing HTTP, allowing correct SOCKS5 usage in Baileys connections.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 6, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts proxy URL handling in the Baileys WhatsApp integration so that the configured protocol (including SOCKS5) is preserved and properly applied when building dynamic proxy URLs, instead of always forcing HTTP, and avoids empty entries from the proxy source list.

Sequence diagram for proxy URL resolution with SOCKS5 support

sequenceDiagram
  actor Admin
  participant BaileysStartupService
  participant Axios
  participant ProxySource
  participant ProxyAgentFactory

  Admin->>BaileysStartupService: startWhatsAppInstance()
  BaileysStartupService->>Axios: get(localProxy.host)
  Axios->>ProxySource: HTTP GET proxy list
  ProxySource-->>Axios: text list host:port per line
  Axios-->>BaileysStartupService: response.data

  BaileysStartupService->>BaileysStartupService: proxyUrls = text.split("\r\n").filter(Boolean)
  BaileysStartupService->>BaileysStartupService: rand = randomIndex(proxyUrls.length)
  BaileysStartupService->>BaileysStartupService: proxyUrl = proxyUrls[rand]
  alt proxyUrl has protocol
    BaileysStartupService->>BaileysStartupService: use proxyUrl as is
  else proxyUrl without protocol
    BaileysStartupService->>BaileysStartupService: proto = localProxy.protocol or http
    BaileysStartupService->>BaileysStartupService: proxyUrl = proto + "://" + proxyUrl
  end

  BaileysStartupService->>ProxyAgentFactory: makeProxyAgent(proxyUrl)
  ProxyAgentFactory-->>BaileysStartupService: agent
  BaileysStartupService->>ProxyAgentFactory: makeProxyAgentUndici(proxyUrl)
  ProxyAgentFactory-->>BaileysStartupService: fetchAgent

  note over BaileysStartupService,ProxyAgentFactory: If proto is socks5, SOCKS5 handshake is used instead of HTTP CONNECT
Loading

File-Level Changes

Change Details Files
Preserve and normalize the protocol when constructing dynamic proxy URLs so SOCKS5 proxies use the correct handshake instead of falling back to HTTP.
  • Filter out empty lines when splitting the proxy list response to avoid selecting invalid proxy entries.
  • Select a random proxy line and treat it as a full URL if it already includes a protocol.
  • If the selected proxy line lacks a protocol, derive the protocol from the configured localProxy.protocol (stripping trailing colon) and fall back to http if unspecified, then prefix it to the proxy host.
  • Pass the normalized proxy URL into makeProxyAgent and makeProxyAgentUndici to ensure the correct protocol-specific agent (including socksDispatcher) is used.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 3 issues, and left some high level feedback:

  • The new proxy selection logic still assumes proxyUrls.length > 0; it might be safer to explicitly handle the case where no valid lines remain after filter(Boolean) (e.g., log and disable the proxy or fall back cleanly) instead of relying on Math.random() with length 0.
  • The inline comment added above the protocol handling is in Spanish; consider converting it to English to stay consistent with the rest of the codebase and make it easier for all contributors to understand.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new proxy selection logic still assumes `proxyUrls.length > 0`; it might be safer to explicitly handle the case where no valid lines remain after `filter(Boolean)` (e.g., log and disable the proxy or fall back cleanly) instead of relying on `Math.random()` with length 0.
- The inline comment added above the protocol handling is in Spanish; consider converting it to English to stay consistent with the rest of the codebase and make it easier for all contributors to understand.

## Individual Comments

### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:610-611` </location>
<code_context>
           const response = await axios.get(this.localProxy?.host);
           const text = response.data;
-          const proxyUrls = text.split('\r\n');
+          const proxyUrls = text.split('\r\n').filter(Boolean);
           const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
-          const proxyUrl = 'http://' + proxyUrls[rand];
</code_context>

<issue_to_address>
**suggestion:** Use a more robust line-splitting strategy for different newline conventions.

Splitting only on `"\r\n"` assumes Windows line endings. If the list is served with Unix-style `"\n"` only, you’ll get a single, newline-containing entry and `filter(Boolean)` won’t fix it. Using something like `text.split(/\r?\n/).filter(Boolean)` will correctly handle both CRLF and LF cases.

```suggestion
          const text = response.data;
          const proxyUrls = text.split(/\r?\n/).filter(Boolean);
```
</issue_to_address>

### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:612-613` </location>
<code_context>
           const text = response.data;
-          const proxyUrls = text.split('\r\n');
+          const proxyUrls = text.split('\r\n').filter(Boolean);
           const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
-          const proxyUrl = 'http://' + proxyUrls[rand];
+          let proxyUrl = proxyUrls[rand];
+          // Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
+          if (!/^\w+:\/\//.test(proxyUrl)) {
</code_context>

<issue_to_address>
**issue:** Handle the case where the filtered proxy list is empty before picking a random entry.

With `.filter(Boolean)`, `proxyUrls` can now be an empty array (e.g., blank/whitespace response). In that case `Math.random() * 0` produces `NaN`, so `proxyUrls[rand]` is `undefined` and gets passed to `makeProxyAgent`. Please add a guard for `proxyUrls.length === 0` and either skip proxy usage or log and fall back before computing `rand`.
</issue_to_address>

### Comment 3
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:613-618` </location>
<code_context>
+          let proxyUrl = proxyUrls[rand];
+          // Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
+          if (!/^\w+:\/\//.test(proxyUrl)) {
+            const proto = this.localProxy?.protocol?.replace(':', '') || 'http';
+            proxyUrl = `${proto}://${proxyUrl}`;
+          }
           options = { agent: makeProxyAgent(proxyUrl), fetchAgent: makeProxyAgentUndici(proxyUrl) };
</code_context>

<issue_to_address>
**suggestion:** Protocol normalization may produce malformed URLs if the configured protocol includes `://`.

If `this.localProxy.protocol` is set to values like `'http://'` or `'http://:'`, using `.replace(':', '')` leaves `proto` as `'http//'`, yielding URLs like `'http///host:port'`. Consider normalizing with something like `protocol.replace(/:.*$/, '')`, stripping `://` explicitly, or enforcing a punctuation-free protocol value at config load time.

```suggestion
          let proxyUrl = proxyUrls[rand];
          // Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
          if (!/^\w+:\/\//.test(proxyUrl)) {
            const rawProtocol = this.localProxy?.protocol?.trim();
            const proto = rawProtocol ? rawProtocol.replace(/:.*$/, '') : 'http';
            proxyUrl = `${proto}://${proxyUrl}`;
          }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 610 to +611
const text = response.data;
const proxyUrls = text.split('\r\n');
const proxyUrls = text.split('\r\n').filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Use a more robust line-splitting strategy for different newline conventions.

Splitting only on "\r\n" assumes Windows line endings. If the list is served with Unix-style "\n" only, you’ll get a single, newline-containing entry and filter(Boolean) won’t fix it. Using something like text.split(/\r?\n/).filter(Boolean) will correctly handle both CRLF and LF cases.

Suggested change
const text = response.data;
const proxyUrls = text.split('\r\n');
const proxyUrls = text.split('\r\n').filter(Boolean);
const text = response.data;
const proxyUrls = text.split(/\r?\n/).filter(Boolean);

Comment on lines 612 to +613
const rand = Math.floor(Math.random() * Math.floor(proxyUrls.length));
const proxyUrl = 'http://' + proxyUrls[rand];
let proxyUrl = proxyUrls[rand];
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: Handle the case where the filtered proxy list is empty before picking a random entry.

With .filter(Boolean), proxyUrls can now be an empty array (e.g., blank/whitespace response). In that case Math.random() * 0 produces NaN, so proxyUrls[rand] is undefined and gets passed to makeProxyAgent. Please add a guard for proxyUrls.length === 0 and either skip proxy usage or log and fall back before computing rand.

Comment on lines +613 to +618
let proxyUrl = proxyUrls[rand];
// Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
if (!/^\w+:\/\//.test(proxyUrl)) {
const proto = this.localProxy?.protocol?.replace(':', '') || 'http';
proxyUrl = `${proto}://${proxyUrl}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Protocol normalization may produce malformed URLs if the configured protocol includes ://.

If this.localProxy.protocol is set to values like 'http://' or 'http://:', using .replace(':', '') leaves proto as 'http//', yielding URLs like 'http///host:port'. Consider normalizing with something like protocol.replace(/:.*$/, ''), stripping :// explicitly, or enforcing a punctuation-free protocol value at config load time.

Suggested change
let proxyUrl = proxyUrls[rand];
// Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
if (!/^\w+:\/\//.test(proxyUrl)) {
const proto = this.localProxy?.protocol?.replace(':', '') || 'http';
proxyUrl = `${proto}://${proxyUrl}`;
}
let proxyUrl = proxyUrls[rand];
// Si la línea ya tiene protocolo, úsala tal cual. Si no, anteponer el protocolo configurado
if (!/^\w+:\/\//.test(proxyUrl)) {
const rawProtocol = this.localProxy?.protocol?.trim();
const proto = rawProtocol ? rawProtocol.replace(/:.*$/, '') : 'http';
proxyUrl = `${proto}://${proxyUrl}`;
}

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.

1 participant