From 9f79917ac930c61e2719a544510586ebc1b418e3 Mon Sep 17 00:00:00 2001 From: Ahmed ElMallah Date: Sat, 25 Jul 2026 15:17:45 -0700 Subject: [PATCH] Redact credentials from endpoint logs Remove URL user information before logging the configured Scorecard API endpoint. Invalid endpoint text is replaced with a fixed marker so malformed credentials cannot reach debug output. Add a registry-level regression test that captures the debug event and proves both username and password are absent. --- internal/registry/builder.go | 12 +++++++++++- internal/registry/builder_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/internal/registry/builder.go b/internal/registry/builder.go index 83f4f063..d84c7bff 100644 --- a/internal/registry/builder.go +++ b/internal/registry/builder.go @@ -3,6 +3,7 @@ package registry import ( "context" "fmt" + "net/url" "sort" "strconv" "strings" @@ -389,12 +390,21 @@ func (r *Registry) registerScorecardMatcher() { r.RegisterMatcherWithOptions(matcher, ComponentOptions{DefaultEnabled: false}) } r.logger.Debug("scorecard matcher configured", - zap.String("api_base", scoreCfg.APIBase), + zap.String("api_base", endpointForLog(scoreCfg.APIBase)), zap.String("cache_dir", scoreCfg.CacheDir), zap.Duration("cache_ttl", scoreCfg.CacheTTL), ) } +func endpointForLog(value string) string { + parsed, err := url.Parse(value) + if err != nil { + return "[invalid URL]" + } + parsed.User = nil + return parsed.String() +} + func (r *Registry) httpClientProvider() *sdk.HTTPClientProvider { if r.httpProvider != nil { return r.httpProvider diff --git a/internal/registry/builder_test.go b/internal/registry/builder_test.go index 87248c04..4151fa49 100644 --- a/internal/registry/builder_test.go +++ b/internal/registry/builder_test.go @@ -2,11 +2,14 @@ package registry import ( "slices" + "strings" "testing" "time" "github.com/bomly-dev/bomly-cli/sdk" "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest/observer" ) func TestBuildScanRegistryRegistersDetectorForEveryPackageManager(t *testing.T) { @@ -155,3 +158,27 @@ func TestRegistryHTTPClientProviderReusesTransport(t *testing.T) { t.Fatalf("timeouts = %v/%v, want 15s/30s", first.Timeout, second.Timeout) } } + +func TestRegisterScorecardMatcherDoesNotLogEndpointCredentials(t *testing.T) { + core, logs := observer.New(zapcore.DebugLevel) + builtins := NewRegistry(Configs{ + ScorecardAPIBase: "https://agent:super-secret@scorecard.example/api", + }, *zap.New(core)) + + builtins.registerScorecardMatcher() + + entries := logs.FilterMessage("scorecard matcher configured").All() + if len(entries) != 1 { + t.Fatalf("scorecard configuration logs = %d, want 1", len(entries)) + } + apiBase, ok := entries[0].ContextMap()["api_base"].(string) + if !ok { + t.Fatalf("api_base log field = %#v, want string", entries[0].ContextMap()["api_base"]) + } + if apiBase != "https://scorecard.example/api" { + t.Fatalf("api_base log field = %q, want URL without credentials", apiBase) + } + if strings.Contains(apiBase, "agent") || strings.Contains(apiBase, "super-secret") { + t.Fatalf("api_base log field leaked credentials: %q", apiBase) + } +}