From cb1b286be0bcf59b4c46a67bef9c391d44462d6d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 10:55:28 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A1=EF=B8=8F=20Sentinel:=20[HIGH]=20Fi?= =?UTF-8?q?x=20SSRF=20vulnerability=20in=20CLI=20executor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the vulnerable struct copy mechanism for URL reconstruction in `executeRemote` with a strict `url.URL{}` struct literal. This ensures only necessary, validated fields (`Scheme`, `Host`, `Path`, `User`, `RawQuery`) are preserved, completely isolating the URL from unintended fields like `Opaque` which can bypass validation during routing. Co-authored-by: rschumann <360788+rschumann@users.noreply.github.com> --- .jules/sentinel.md | 4 ++++ internal/runtime/executor/cli_executor.go | 15 +++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 33f2d09a..eb194ad3 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -361,3 +361,7 @@ Remember: You're Sentinel, the guardian of switchAILocal. Security is not option **Vulnerability:** The CLI executor proxy lacked scheme validation for the user-controlled `remoteHost`, creating an SSRF vulnerability. The OAuth callback forwarder lacked prefix validation for `targetBase`, creating an open redirect vulnerability. **Learning:** Taint analysis revealed that input passed from management configurations or remote host specifications must be strictly validated before being used in HTTP requests or redirect targets to prevent protocol abuse and unauthorized redirects. **Prevention:** Always enforce strict protocol validation (e.g., scheme is 'http' or 'https') for outbound requests and validate redirect targets against allowed prefixes (e.g., 'http://localhost' or '/'). +## 2026-08-23 - Secure URL Reconstruction +**Vulnerability:** SSRF via taint analysis in CLI Executor where target URL was modified by copying the parsed struct and nullifying fields. +**Learning:** Copying a parsed `url.URL` struct and nullifying fields (like `RawPath`) is insufficient if an attacker provides a URL that populates the `Opaque` field. The `.String()` method prioritizes `Opaque`, allowing attackers to bypass validation and routing logic. +**Prevention:** Always reconstruct target URLs from untrusted input by explicitly creating a new `url.URL{}` struct literal, copying only the required and validated fields (e.g., `Scheme`, `Host`, `Path`). diff --git a/internal/runtime/executor/cli_executor.go b/internal/runtime/executor/cli_executor.go index 419ece19..fec9877a 100644 --- a/internal/runtime/executor/cli_executor.go +++ b/internal/runtime/executor/cli_executor.go @@ -521,13 +521,16 @@ func (e *LocalCLIExecutor) executeRemote(ctx context.Context, remoteHost, binary return switchailocalexecutor.Response{}, fmt.Errorf("invalid remote host URL, must use http or https scheme and include a host") } - targetURL := *parsedURL - targetURL.Path = strings.TrimSuffix(targetURL.Path, "/") + "/run" - targetURL.RawPath = "" - targetURL.Fragment = "" - targetURL.RawFragment = "" + // Securely construct URL to prevent SSRF bypasses via Opaque or other fields. + safeURL := url.URL{ + Scheme: parsedURL.Scheme, + User: parsedURL.User, + Host: parsedURL.Host, + Path: strings.TrimSuffix(parsedURL.Path, "/") + "/run", + RawQuery: parsedURL.RawQuery, + } - httpReq, err := http.NewRequestWithContext(ctx, "POST", targetURL.String(), bytes.NewReader(jsonBody)) + httpReq, err := http.NewRequestWithContext(ctx, "POST", safeURL.String(), bytes.NewReader(jsonBody)) if err != nil { return switchailocalexecutor.Response{}, fmt.Errorf("failed to create bridge request: %w", err) }