From 758a01ab7414b80442cd5632d8467552152d2fc8 Mon Sep 17 00:00:00 2001 From: Abhinav Raut Date: Fri, 17 Jul 2026 19:24:31 +0530 Subject: [PATCH] guard knowledge base URL import against SSRF and fix OIDC transport defaults The earlier SSRF work covered the provider, custom-tool, webhook, and OIDC outbound calls but missed the knowledge base URL import path, which still used http.DefaultClient. An admin importing a URL could reach loopback, link-local, or RFC1918 hosts. Renamed the Manager's toolClient to httpClient (it is a general SSRF-guarded outbound client now) and pointed fetchURL at it. The guard sits on the dialer's Control, so it also runs on redirect targets, not just the first hop. Also fixed the OIDC discovery client. Its hand-built transport dropped two defaults that http.DefaultTransport sets. Added Proxy: http.ProxyFromEnvironment so it honors HTTP_PROXY/HTTPS_PROXY (SSO discovery now works behind an egress proxy) and ForceAttemptHTTP2 so HTTP/2 is negotiated. --- internal/ai/ai.go | 4 ++-- internal/ai/tools.go | 2 +- internal/ai/urlimport.go | 2 +- internal/auth/auth.go | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/ai/ai.go b/internal/ai/ai.go index 574f35c8..5f87c926 100644 --- a/internal/ai/ai.go +++ b/internal/ai/ai.go @@ -50,7 +50,7 @@ type Manager struct { snippetGenMu sync.Mutex snippetGen map[int]uint64 dialControl ssrf.Control - toolClient *http.Client + httpClient *http.Client } // Opts contains options for initializing the Manager. @@ -111,7 +111,7 @@ func New(opts Opts) (*Manager, error) { index: newEmbeddingIndex(), snippetGen: make(map[int]uint64), dialControl: opts.DialControl, - toolClient: &http.Client{ + httpClient: &http.Client{ Timeout: 20 * time.Second, Transport: &http.Transport{ DialContext: (&net.Dialer{ diff --git a/internal/ai/tools.go b/internal/ai/tools.go index 6df5d8a7..5d8e21f6 100644 --- a/internal/ai/tools.go +++ b/internal/ai/tools.go @@ -247,7 +247,7 @@ func (m *Manager) buildToolRegistry(tctx ToolContext, allowedToolIDs []int, incl m.lo.Warn("skipping custom tool that collides with a built-in tool", "name", ct.Name) continue } - ht := newHTTPTool(ct, m.encryptionKey, m.lo, m.toolClient, tctx) + ht := newHTTPTool(ct, m.encryptionKey, m.lo, m.httpClient, tctx) registry[ht.Name()] = ht defs = append(defs, toolDef(ht)) } diff --git a/internal/ai/urlimport.go b/internal/ai/urlimport.go index c56fb454..02c81fe3 100644 --- a/internal/ai/urlimport.go +++ b/internal/ai/urlimport.go @@ -56,7 +56,7 @@ func (m *Manager) fetchURL(ctx context.Context, pageURL string) (string, string, return "", "", err } req.Header.Set("User-Agent", "libredesk") - resp, err := http.DefaultClient.Do(req) + resp, err := m.httpClient.Do(req) if err != nil { return "", "", err } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index c42f4fd5..24a89d60 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -137,11 +137,13 @@ func newOIDCClient(dialControl ssrf.Control) *http.Client { return &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 3 * time.Second, KeepAlive: 30 * time.Second, Control: dialControl, }).DialContext, + ForceAttemptHTTP2: true, TLSHandshakeTimeout: 5 * time.Second, ResponseHeaderTimeout: 5 * time.Second, },