fix(slack): honor HTTPS_PROXY for Socket Mode WebSocket connections#62878
fix(slack): honor HTTPS_PROXY for Socket Mode WebSocket connections#62878steipete merged 4 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes Slack Socket Mode ignoring proxy env vars by creating an Confidence Score: 5/5Safe to merge; the only finding is a P2 gap where NO_PROXY exclusions are not honored, which doesn't affect the primary use case. All findings are P2. The proxy detection and agent wiring are correct for the stated goal (proxy-only environments). The NO_PROXY gap is a completeness concern, not a present breakage for the target users. extensions/slack/src/client.ts — resolveSlackProxyAgent does not check NO_PROXY/no_proxy.
|
| function resolveSlackProxyAgent(): HttpsProxyAgent<string> | undefined { | ||
| // Match undici EnvHttpProxyAgent semantics: lower-case takes precedence, | ||
| // HTTPS prefers https_proxy then falls back to http_proxy. | ||
| const proxyUrl = | ||
| process.env.https_proxy?.trim() || | ||
| process.env.HTTPS_PROXY?.trim() || | ||
| process.env.http_proxy?.trim() || | ||
| process.env.HTTP_PROXY?.trim() || | ||
| undefined; | ||
| if (!proxyUrl) { | ||
| return undefined; | ||
| } | ||
| return new HttpsProxyAgent<string>(proxyUrl); | ||
| } |
There was a problem hiding this comment.
HttpsProxyAgent does not respect the NO_PROXY / no_proxy exclusion list, so any host matched by NO_PROXY would still be tunneled through the proxy. The PR description claims undici EnvHttpProxyAgent semantics, but EnvHttpProxyAgent is the component that handles NO_PROXY exclusions — HttpsProxyAgent alone does not.
The core uses EnvHttpProxyAgent (via resolveProxyFetchFromEnv in src/infra/net/proxy-fetch.ts) precisely because it handles NO_PROXY. For the WebSocket agent slot that ws requires, consider checking NO_PROXY / no_proxy before constructing the agent and short-circuiting to undefined when the target host is excluded. Alternatively, document explicitly that NO_PROXY is not respected for Socket Mode connections.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/slack/src/client.ts
Line: 27-40
Comment:
**`NO_PROXY` not honored**
`HttpsProxyAgent` does not respect the `NO_PROXY` / `no_proxy` exclusion list, so any host matched by `NO_PROXY` would still be tunneled through the proxy. The PR description claims undici `EnvHttpProxyAgent` semantics, but `EnvHttpProxyAgent` is the component that handles `NO_PROXY` exclusions — `HttpsProxyAgent` alone does not.
The core uses `EnvHttpProxyAgent` (via `resolveProxyFetchFromEnv` in `src/infra/net/proxy-fetch.ts`) precisely because it handles `NO_PROXY`. For the WebSocket `agent` slot that `ws` requires, consider checking `NO_PROXY` / `no_proxy` before constructing the agent and short-circuiting to `undefined` when the target host is excluded. Alternatively, document explicitly that `NO_PROXY` is not respected for Socket Mode connections.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 071c7da815
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| process.env.https_proxy?.trim() || | ||
| process.env.HTTPS_PROXY?.trim() || | ||
| process.env.http_proxy?.trim() || | ||
| process.env.HTTP_PROXY?.trim() || |
There was a problem hiding this comment.
Treat empty lowercase proxy vars as authoritative
Use lower-case proxy env vars as authoritative even when they are intentionally blank. With the current || chain, https_proxy="" (or whitespace) falls back to HTTPS_PROXY, which contradicts the repo’s documented EnvHttpProxyAgent-style precedence and can unexpectedly force Slack traffic through a proxy that operators explicitly disabled for this process.
Useful? React with 👍 / 👎.
| if (!proxyUrl) { | ||
| return undefined; | ||
| } | ||
| return new HttpsProxyAgent<string>(proxyUrl); |
There was a problem hiding this comment.
Guard proxy agent creation against malformed env URLs
Handle invalid proxy env values defensively before constructing the agent. new HttpsProxyAgent(proxyUrl) is called without validation or a fallback path, so a malformed HTTPS_PROXY/HTTP_PROXY value can fail option resolution during startup and prevent the Slack channel from initializing, whereas this path should degrade gracefully to direct connectivity.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4e968f9ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return true; | ||
| } | ||
| // Exact match or suffix match (with leading dot) | ||
| if (lower === entry || lower.endsWith(entry.startsWith(".") ? entry : `.${entry}`)) { |
There was a problem hiding this comment.
Honor leading-dot NO_PROXY for apex slack.com
Treating a leading-dot entry as a pure suffix check misses the apex host: with NO_PROXY=.slack.com, resolveSlackProxyAgent() checks isHostExcludedByNoProxy("slack.com"), but "slack.com".endsWith(".slack.com") is false, so the proxy agent is still applied. This breaks a common exclusion pattern and can force Slack traffic through a proxy even when operators explicitly excluded the Slack domain.
Useful? React with 👍 / 👎.
When HTTPS_PROXY or HTTP_PROXY env vars are set, create an HttpsProxyAgent and pass it as the `agent` option through @slack/bolt → @slack/socket-mode → ws, so the WebSocket upgrade request is tunneled through the proxy. This fixes Slack Socket Mode in environments where all outbound traffic must go through an HTTP CONNECT proxy (e.g. sandboxed containers, corporate networks). Previously the ws library opened a direct connection to wss-primary.slack.com, ignoring proxy env vars entirely. The approach mirrors the existing Discord gateway proxy support (extensions/discord/src/monitor/gateway-plugin.ts) which uses the same https-proxy-agent library. Fixes openclaw#57405
- Check NO_PROXY/no_proxy before creating HttpsProxyAgent; skip proxy when slack.com matches an exclusion entry (exact, suffix, or wildcard). - Wrap HttpsProxyAgent construction in try/catch so malformed proxy URLs degrade to direct connectivity instead of crashing Slack channel init. - Extract resolveProxyUrlFromEnv and isHostExcludedByNoProxy as testable helpers. - Add tests for NO_PROXY exclusion, wildcard, unrelated hosts, and malformed URL resilience.
`.slack.com` in NO_PROXY should match both `slack.com` (apex) and `wss-primary.slack.com` (subdomain). Strip the leading dot before comparison so the suffix check works for both cases.
bf16be9 to
fb32cf8
Compare
|
Landed via temp rebase onto main.
Thanks @mjamiv! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fb32cf822b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return undefined; | ||
| } | ||
| // Slack Socket Mode connects to these hosts; skip proxy if excluded. | ||
| if (isHostExcludedByNoProxy("slack.com")) { |
There was a problem hiding this comment.
Evaluate NO_PROXY against Socket Mode hostnames
resolveSlackProxyAgent() only checks NO_PROXY against the apex slack.com, but Socket Mode WebSockets connect to concrete hosts like wss-primary.slack.com / wss-backup.slack.com. When operators use targeted exclusions such as NO_PROXY=wss-primary.slack.com, this condition never matches, so we still attach HttpsProxyAgent and force the WebSocket through the proxy unexpectedly. That can break connectivity in environments that rely on host-specific bypass rules.
Useful? React with 👍 / 👎.
The Discord extension's gateway plugin relies on @buape/carbon via ws, which doesn't honour HTTPS_PROXY and opens a direct TCP socket to gateway.discord.gg. The sandbox netns blocks direct egress, so the WSS handshake never reaches Discord and the bot loops on close-code 1006. DiscordAccountConfig already exposes a first-class proxy field that the gateway plugin threads through to the ws agent option, so extending the existing Telegram bake to Discord is enough to route the upgrade through the OpenShell proxy. The pattern precedent for this NemoClaw-side workaround is openclaw/openclaw#62878 (Slack Socket Mode), openclaw/openclaw#61541 (WhatsApp), and openclaw/openclaw#41835 (Feishu) — each a mirror of the same ignores-env-vars footgun in a different channel's client lib. Remove once OpenClaw lands an equivalent env-var- honouring fix for the Discord gateway. Fixes #1738 <!-- markdownlint-disable MD041 --> ## Summary <!-- 1-3 sentences: what this PR does and why. --> ## Related Issue <!-- Fixes #NNN or Closes #NNN. Remove this section if none. --> ## Changes <!-- Bullet list of key changes. --> ## Type of Change - [X] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Verification <!-- Check each item you ran and confirmed. Leave unchecked items you skipped. --> - [X] `npx prek run --all-files` passes - [X] `npm test` passes - [ ] Tests added or updated for new or changed behavior - [ ] No secrets, API keys, or credentials committed - [ ] Docs updated for user-facing behavior changes - [ ] `make docs` builds without warnings (doc changes only) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) ## AI Disclosure <!-- If an AI agent authored or co-authored this PR, check the box and name the tool. Remove this section for fully human-authored PRs. --> - [X] AI-assisted — tool: Claude Code<!-- e.g., Claude Code, Cursor, GitHub Copilot --> --- <!-- DCO sign-off required by CI. Run: git config user.name && git config user.email --> Signed-off-by: Tinson Lai <tinsonl@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Discord proxy routing is now aligned with Telegram's proxy routing configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Signed-off-by: Tinson Lai <tinsonl@nvidia.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com>
Summary
Slack Socket Mode ignores
HTTPS_PROXY/HTTP_PROXYenv vars — thewslibrary opens a direct WebSocket connection towss-primary.slack.com, bypassing any configured proxy. This breaks Socket Mode in proxy-only environments (sandboxed containers, corporate networks, NVIDIA OpenShell).This PR creates an
HttpsProxyAgentfrom proxy env vars and passes it as theagentoption through@slack/bolt→@slack/socket-mode→SlackWebSocket→ws, so the WebSocket upgrade request is tunneled through the HTTP CONNECT proxy.Changes
extensions/slack/src/client.ts:resolveSlackWebClientOptions()andresolveSlackWriteClientOptions()now setagentto anHttpsProxyAgentwhenHTTPS_PROXY/HTTP_PROXYenv vars are present. Follows undiciEnvHttpProxyAgentsemantics (lower-case takes precedence).extensions/slack/package.json: Addedhttps-proxy-agentdependency (same version as Discord extension).extensions/slack/src/client.test.ts: Tests for proxy agent creation, env var precedence, explicit agent override, and no-op when no proxy is configured.How it works
The
agentflows through:resolveSlackWebClientOptions()→{ agent: HttpsProxyAgent }new App({ clientOptions })(Bolt)new SocketModeReceiver({ clientOptions })→new SocketModeClient({ clientOptions })SlackWebSocket({ httpAgent: clientOptions.agent })new ws.WebSocket(url, { agent: httpAgent })— tunneled through proxy ✅Prior art
Mirrors the existing Discord gateway proxy support in
extensions/discord/src/monitor/gateway-plugin.ts, which uses the samehttps-proxy-agentlibrary.Production validation
We've been running an equivalent monkey-patch (
openclaw-ws-proxy-patch.js) across 4 OpenClaw agents on NVIDIA OpenShell sandboxes since March 2026, routing all Slack Socket Mode traffic through an HTTP CONNECT proxy at10.200.0.1:3128. This PR replaces that workaround with a proper fix.Fixes #57405
Test plan
HTTPS_PROXYset → Socket Mode connects through proxyclientOptions.agent→ not overridden by proxy detectionHTTP_PROXYfallback whenHTTPS_PROXYnot sethttps_proxytakes precedence over upper-caseHTTPS_PROXYextensions/slack/src/client.test.ts