fix(provider): harden proxy path stripping

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 20:08:55 +00:00
parent cd4e24d15f
commit c9627729b5
4 changed files with 60 additions and 7 deletions

View file

@ -1,4 +1,4 @@
// SPDX-Licence-Identifier: EUPL-1.2
// SPDX-License-Identifier: EUPL-1.2
// Package provider defines the Service Provider Framework interfaces.
//

View file

@ -1,4 +1,4 @@
// SPDX-Licence-Identifier: EUPL-1.2
// SPDX-License-Identifier: EUPL-1.2
package provider
@ -59,11 +59,10 @@ func NewProxy(cfg ProxyConfig) *ProxyProvider {
proxy.Director = func(req *http.Request) {
defaultDirector(req)
// Strip the base path prefix from the request path.
req.URL.Path = strings.TrimPrefix(req.URL.Path, basePath)
if req.URL.Path == "" {
req.URL.Path = "/"
req.URL.Path = stripBasePath(req.URL.Path, basePath)
if req.URL.RawPath != "" {
req.URL.RawPath = stripBasePath(req.URL.RawPath, basePath)
}
req.URL.RawPath = strings.TrimPrefix(req.URL.RawPath, basePath)
}
return &ProxyProvider{
@ -72,6 +71,34 @@ func NewProxy(cfg ProxyConfig) *ProxyProvider {
}
}
// stripBasePath removes an exact base path prefix from a request path.
// It only strips when the path matches the base path itself or lives under
// the base path boundary, so "/api" will not accidentally trim "/api-v2".
func stripBasePath(path, basePath string) string {
basePath = strings.TrimSuffix(strings.TrimSpace(basePath), "/")
if basePath == "" || basePath == "/" {
if path == "" {
return "/"
}
return path
}
if path == basePath {
return "/"
}
prefix := basePath + "/"
if strings.HasPrefix(path, prefix) {
trimmed := strings.TrimPrefix(path, basePath)
if trimmed == "" {
return "/"
}
return trimmed
}
return path
}
// Name returns the provider identity.
func (p *ProxyProvider) Name() string {
return p.config.Name

View file

@ -0,0 +1,26 @@
// SPDX-License-Identifier: EUPL-1.2
package provider
import "testing"
func TestStripBasePath_Good_ExactBoundary(t *testing.T) {
got := stripBasePath("/api/v1/cool-widget/items", "/api/v1/cool-widget")
if got != "/items" {
t.Fatalf("expected stripped path %q, got %q", "/items", got)
}
}
func TestStripBasePath_Good_RootPath(t *testing.T) {
got := stripBasePath("/api/v1/cool-widget", "/api/v1/cool-widget")
if got != "/" {
t.Fatalf("expected stripped root path %q, got %q", "/", got)
}
}
func TestStripBasePath_Good_DoesNotTrimPartialPrefix(t *testing.T) {
got := stripBasePath("/api/v1/cool-widget-2/items", "/api/v1/cool-widget")
if got != "/api/v1/cool-widget-2/items" {
t.Fatalf("expected partial prefix to remain unchanged, got %q", got)
}
}

View file

@ -1,4 +1,4 @@
// SPDX-Licence-Identifier: EUPL-1.2
// SPDX-License-Identifier: EUPL-1.2
package provider