fix: honor Slack Socket Mode env proxies (#62878) (thanks @mjamiv)

This commit is contained in:
Peter Steinberger
2026-04-08 05:36:57 +01:00
parent 5609a35f67
commit b4034b32c3
4 changed files with 49 additions and 27 deletions

View File

@@ -4,6 +4,10 @@ Docs: https://docs.openclaw.ai
## Unreleased
### Fixes
- Slack: honor ambient HTTP(S) proxy settings for Socket Mode WebSocket connections, including NO_PROXY exclusions, so proxy-only deployments can connect without a monkey patch. (#62878) Thanks @mjamiv.
## 2026.4.7-1
### Fixes

View File

@@ -85,7 +85,14 @@ describe("slack web client config", () => {
describe("slack proxy agent", () => {
const originalEnv = { ...process.env };
const PROXY_KEYS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy", "NO_PROXY", "no_proxy"];
const PROXY_KEYS = [
"HTTPS_PROXY",
"HTTP_PROXY",
"https_proxy",
"http_proxy",
"NO_PROXY",
"no_proxy",
];
beforeEach(() => {
for (const key of PROXY_KEYS) {
@@ -139,7 +146,17 @@ describe("slack proxy agent", () => {
expect(options.agent).toBeDefined();
// HttpsProxyAgent stores the proxy URL — verify it picked the lower-case one
expect((options.agent as { proxy: { href: string } }).proxy.href).toContain("lower.example.com");
expect((options.agent as unknown as { proxy: { href: string } }).proxy.href).toContain(
"lower.example.com",
);
});
it("treats empty lowercase https_proxy as authoritative over uppercase", () => {
process.env.https_proxy = "";
process.env.HTTPS_PROXY = "http://upper.example.com:3128";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
});
it("also applies proxy agent to write client options", () => {
@@ -166,6 +183,14 @@ describe("slack proxy agent", () => {
expect(options.agent).toBeUndefined();
});
it("respects space-separated no_proxy entries", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.no_proxy = "localhost *.slack.com";
const options = resolveSlackWebClientOptions();
expect(options.agent).toBeUndefined();
});
it("respects NO_PROXY wildcard", () => {
process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
process.env.NO_PROXY = "*";

View File

@@ -1,5 +1,6 @@
import { type RetryOptions, type WebClientOptions, WebClient } from "@slack/web-api";
import { HttpsProxyAgent } from "https-proxy-agent";
import { resolveEnvHttpProxyUrl } from "openclaw/plugin-sdk/infra-runtime";
export const SLACK_DEFAULT_RETRY_OPTIONS: RetryOptions = {
retries: 2,
@@ -17,23 +18,27 @@ export const SLACK_WRITE_RETRY_OPTIONS: RetryOptions = {
* Check whether a hostname is excluded from proxying by `NO_PROXY` / `no_proxy`.
* Supports comma-separated entries with optional leading dots (e.g. `.slack.com`).
*/
function isHostExcludedByNoProxy(
hostname: string,
env: NodeJS.ProcessEnv = process.env,
): boolean {
function isHostExcludedByNoProxy(hostname: string, env: NodeJS.ProcessEnv = process.env): boolean {
const raw = env.no_proxy ?? env.NO_PROXY;
if (!raw) {
return false;
}
const entries = raw.split(",").map((e) => e.trim().toLowerCase()).filter(Boolean);
const entries = raw
.split(/[,\s]+/)
.map((e) => e.trim().toLowerCase())
.filter(Boolean);
const lower = hostname.toLowerCase();
for (const entry of entries) {
if (entry === "*") {
return true;
}
// Strip optional leading dot for comparison so `.slack.com` matches both
// `slack.com` (apex) and `wss-primary.slack.com` (subdomain).
const bare = entry.startsWith(".") ? entry.slice(1) : entry;
// Strip optional wildcard/leading dot so `*.slack.com` and `.slack.com`
// match both `slack.com` (apex) and Slack subdomains.
const bare = entry.startsWith("*.")
? entry.slice(2)
: entry.startsWith(".")
? entry.slice(1)
: entry;
if (lower === bare || lower.endsWith(`.${bare}`)) {
return true;
}
@@ -41,21 +46,6 @@ function isHostExcludedByNoProxy(
return false;
}
/**
* Resolve the proxy URL from env vars following undici EnvHttpProxyAgent
* semantics: lower-case takes precedence, HTTPS prefers https_proxy then
* falls back to http_proxy. Returns `undefined` when no proxy is configured.
*/
function resolveProxyUrlFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {
return (
env.https_proxy?.trim() ||
env.HTTPS_PROXY?.trim() ||
env.http_proxy?.trim() ||
env.HTTP_PROXY?.trim() ||
undefined
);
}
/**
* Build an HTTPS proxy agent from env vars (HTTPS_PROXY, HTTP_PROXY, etc.)
* for use as the `agent` option in Slack WebClient and Socket Mode connections.
@@ -72,7 +62,7 @@ function resolveProxyUrlFromEnv(env: NodeJS.ProcessEnv = process.env): string |
* are excluded by `NO_PROXY`.
*/
function resolveSlackProxyAgent(): HttpsProxyAgent<string> | undefined {
const proxyUrl = resolveProxyUrlFromEnv();
const proxyUrl = resolveEnvHttpProxyUrl("https");
if (!proxyUrl) {
return undefined;
}
@@ -81,7 +71,7 @@ function resolveSlackProxyAgent(): HttpsProxyAgent<string> | undefined {
return undefined;
}
try {
return new HttpsProxyAgent<string>(proxyUrl);
return new HttpsProxyAgent(proxyUrl);
} catch {
// Malformed proxy URL — degrade gracefully to direct connection.
return undefined;

3
pnpm-lock.yaml generated
View File

@@ -941,6 +941,9 @@ importers:
'@slack/web-api':
specifier: ^7.15.0
version: 7.15.0
https-proxy-agent:
specifier: ^9.0.0
version: 9.0.0
devDependencies:
'@openclaw/plugin-sdk':
specifier: workspace:*