Files
OmniRoute/open-sse/utils/proxyFamily.ts
Diego Rodrigues de Sa e Souza 7219f2b38d feat(proxy): IPv6-only egress enforcement + close IP-leak paths (#3777)
Implements end-to-end proxy egress isolation with address-family enforcement and closes four real IP-leak paths surfaced by the proxy subsystem audit.

IPv6-only egress:
- Detect proxy family (IPv6/IPv4 literal, or per-account auto/ipv4/ipv6 directive) and pin the connect family (family:6/4 + autoSelectFamily:false) on both ProxyAgent (proxyTls) and a custom SOCKS connector that threads socket_options.family into SocksClient (fetch-socks can't), so Happy Eyeballs cannot pick IPv4 for an IPv6-only policy.
- De-bracket IPv6-literal proxy hosts (socksOptions.host + proxyHealth tcpCheck) — fixes ENOTFOUND on [::1]-style SOCKS proxies and health checks.
- Fail-closed when an IPv6-only hostname proxy has no AAAA (PROXY_FAMILY_UNAVAILABLE) instead of leaking over v4.
- DB migration 099 adds the proxy family column; family is preserved through the resolveProxyForConnection cascade and proxies/upstreamProxy modules.

Leak fixes:
- L1: web TLS clients (grok/claude/chatgpt/perplexity) now fail-closed on proxy-resolution error instead of silently going direct (was the highest-risk asymmetry).
- L2: safeResolveProxy fail-closed by default (PROXY_FAIL_OPEN opt-out).
- L3: API-key usage/quota fetch routed through the connection proxy context.
- L4: NVIDIA validation proxy bypass (#3226) documented.

Tests: 46 TDD unit tests + BDD egress-isolation matrix. typecheck:core + eslint clean.
Gemini 'critical' SOCKS finding verified as a false positive (socks accepts proxy.host; see PR thread).

Integrated into release/v3.8.24.
2026-06-13 14:18:09 -03:00

25 lines
847 B
TypeScript

import { isIP } from "node:net";
export type ProxyFamily = "auto" | "ipv4" | "ipv6";
/** Remove the surrounding brackets from an IPv6 literal host (`[::1]` -> `::1`). */
export function stripIpv6Brackets(host: string): string {
if (typeof host !== "string") return "";
if (host.startsWith("[") && host.endsWith("]")) {
return host.slice(1, -1);
}
return host;
}
/** 4 / 6 if the host is an IP literal (brackets tolerated), null if it is a hostname. */
export function detectIpLiteralFamily(host: string): 4 | 6 | null {
const bare = stripIpv6Brackets(host);
const v = isIP(bare);
return v === 0 ? null : (v as 4 | 6);
}
/** Normalize a stored family directive; anything unknown means "auto". */
export function parseProxyFamily(value: unknown): ProxyFamily {
return value === "ipv4" || value === "ipv6" ? value : "auto";
}