diff --git a/internal/handlers/npm_registry.go b/internal/handlers/npm_registry.go index 02c7ad8..a5b2876 100644 --- a/internal/handlers/npm_registry.go +++ b/internal/handlers/npm_registry.go @@ -101,6 +101,14 @@ func (h *NPMRegistryHandler) HandleRequest(req *http.Request, ctx *goproxy.Proxy continue } + // Path-segment-aware matching prevents credentials configured for one + // path-scoped registry from being applied to sibling paths on the same + // host (e.g., /team-a-npm should not match /team-b-npm). + regPath := strings.TrimSuffix(regURL.Path, "/") + if regPath != "" && !strings.HasPrefix(req.URL.Path, regPath+"/") && req.URL.Path != regPath { + continue + } + if cred.token == "" && cred.password != "" { cred.token = cred.username + ":" + cred.password } diff --git a/internal/handlers/npm_registry_test.go b/internal/handlers/npm_registry_test.go index d89f1c7..f68d70c 100644 --- a/internal/handlers/npm_registry_test.go +++ b/internal/handlers/npm_registry_test.go @@ -59,9 +59,10 @@ func TestNPMRegistryHandler(t *testing.T) { req = handleRequestAndClose(handler, req, nil) assertHasTokenAuth(t, req, "Bearer", privateRegToken, "valid registry request with port and path") + // Sibling path on the same host should NOT receive credentials from /reg-path req = httptest.NewRequest("GET", "https://example.com/other-path/private-package", nil) req = handleRequestAndClose(handler, req, nil) - assertHasTokenAuth(t, req, "Bearer", privateRegToken, "different path") + assertUnauthenticated(t, req, "sibling path should not match") req = httptest.NewRequest("GET", "https://nexus.some-company.com/private-package", nil) req = handleRequestAndClose(handler, req, nil) @@ -87,3 +88,36 @@ func TestNPMRegistryHandler(t *testing.T) { req = handleRequestAndClose(handler, req, nil) assertHasBasicAuth(t, req, nexusUser, nexusPassword, "azure devops case insensitive registry request") } + +func TestNPMRegistryHandler_SameHostDifferentPaths(t *testing.T) { + teamAToken := "team-a-token" + teamBToken := "team-b-token" + credentials := config.Credentials{ + config.Credential{ + "type": "npm_registry", + "registry": "https://artifactory.example.com/api/npm/team-a-npm", + "token": teamAToken, + }, + config.Credential{ + "type": "npm_registry", + "registry": "https://artifactory.example.com/api/npm/team-b-npm", + "token": teamBToken, + }, + } + handler := NewNPMRegistryHandler(credentials) + + // Request to team-a path should use team-a token + req := httptest.NewRequest("GET", "https://artifactory.example.com/api/npm/team-a-npm/@scope/pkg", nil) + req = handleRequestAndClose(handler, req, nil) + assertHasTokenAuth(t, req, "Bearer", teamAToken, "team-a path should use team-a token") + + // Request to team-b path should use team-b token, not team-a + req = httptest.NewRequest("GET", "https://artifactory.example.com/api/npm/team-b-npm/@scope/pkg", nil) + req = handleRequestAndClose(handler, req, nil) + assertHasTokenAuth(t, req, "Bearer", teamBToken, "team-b path should use team-b token") + + // Request to unrelated path should not be authenticated + req = httptest.NewRequest("GET", "https://artifactory.example.com/api/npm/team-c-npm/@scope/pkg", nil) + req = handleRequestAndClose(handler, req, nil) + assertUnauthenticated(t, req, "unrelated path should not match any credential") +}