Guard vetting agent HTTP tools against SSRF
The third-party vetting agent runs a suite of HTTP "security" tools on
the internal worker network against a caller-supplied URL that is only
validated for length and charset, not host. Several tools reached
internal, loopback, and link-local addresses:
- analyze_csp used a bare http.Client with no host validation, no
redirect control, and no rebinding-safe transport, reflecting the
target's CSP header back to the caller.
- check_security_headers, fetch_robots_txt, and fetch_sitemap
validated only the initial host, then followed 3xx redirects with an
ordinary client, yielding full-read SSRF via a redirect to an
internal address.
- check_cors validated the URL but still dialed through an ordinary
transport, leaving it exposed to DNS-rebinding TOCTOU.
Route every one of these clients through the house-standard
httpclient.DefaultPooledClient(WithSSRFProtection()), which rejects
dials to loopback, private, CGNAT, link-local, ULA, IPv4-mapped, and
reserved ranges on the resolved peer IP at connect time (defeating DNS
rebinding on every redirect hop) and refuses cross-origin redirects.
download_pdf moves onto the same client, and the now-unused local
netcheck.NewPinnedTransport is removed. analyze_csp also gains an
up-front ValidatePublicURL check for a clean early error and scheme
enforcement.
Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
@@ -27,8 +27,8 @@ import (
|
||||
|
||||
"github.com/pdfcpu/pdfcpu/pkg/api"
|
||||
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
||||
"go.gearno.de/kit/httpclient"
|
||||
"go.probo.inc/probo/pkg/agent"
|
||||
"go.probo.inc/probo/pkg/agent/tools/internal/netcheck"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -44,10 +44,8 @@ type (
|
||||
)
|
||||
|
||||
func DownloadPDFTool() agent.Tool {
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: netcheck.NewPinnedTransport(),
|
||||
}
|
||||
client := httpclient.DefaultPooledClient(httpclient.WithSSRFProtection())
|
||||
client.Timeout = 30 * time.Second
|
||||
|
||||
return agent.FunctionTool(
|
||||
"download_pdf",
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/httpclient"
|
||||
"go.probo.inc/probo/pkg/agent"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,8 @@ type (
|
||||
)
|
||||
|
||||
func FetchRobotsTxtTool() agent.Tool {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
client := httpclient.DefaultPooledClient(httpclient.WithSSRFProtection())
|
||||
client.Timeout = 10 * time.Second
|
||||
|
||||
return agent.FunctionTool(
|
||||
"fetch_robots_txt",
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/httpclient"
|
||||
"go.probo.inc/probo/pkg/agent"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,8 @@ const (
|
||||
)
|
||||
|
||||
func FetchSitemapTool() agent.Tool {
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
client := httpclient.DefaultPooledClient(httpclient.WithSSRFProtection())
|
||||
client.Timeout = 15 * time.Second
|
||||
|
||||
return agent.FunctionTool(
|
||||
"fetch_sitemap",
|
||||
|
||||
Reference in New Issue
Block a user