diff --git a/internal/auth/header.go b/internal/auth/header.go index 9b732f097..8f371367b 100644 --- a/internal/auth/header.go +++ b/internal/auth/header.go @@ -16,8 +16,12 @@ package auth import ( "errors" "strings" + + "github.com/githubnext/gh-aw-mcpg/internal/logger" ) +var log = logger.New("auth:header") + var ( // ErrMissingAuthHeader is returned when the Authorization header is missing ErrMissingAuthHeader = errors.New("missing Authorization header") @@ -25,6 +29,18 @@ var ( ErrInvalidAuthHeader = errors.New("invalid Authorization header format") ) +// sanitizeForLogging returns a sanitized version of the input string for safe logging. +// It shows only the first 4 characters followed by "..." to prevent exposing sensitive data. +// For strings with 4 or fewer characters, it returns only "...". +func sanitizeForLogging(input string) string { + if len(input) > 4 { + return input[:4] + "..." + } else if len(input) > 0 { + return "..." + } + return "" +} + // ParseAuthHeader parses the Authorization header and extracts the API key and agent ID. // Per MCP spec 7.1, the Authorization header should contain the API key directly // without any Bearer prefix or other scheme. @@ -38,33 +54,45 @@ var ( // - agentID: The extracted agent/session identifier // - error: ErrMissingAuthHeader if header is empty, nil otherwise func ParseAuthHeader(authHeader string) (apiKey string, agentID string, error error) { + log.Printf("Parsing auth header: sanitized=%s, length=%d", sanitizeForLogging(authHeader), len(authHeader)) + if authHeader == "" { + log.Print("Auth header missing, returning error") return "", "", ErrMissingAuthHeader } // Handle "Bearer " format (backward compatibility) if strings.HasPrefix(authHeader, "Bearer ") { + log.Print("Detected Bearer token format (backward compatibility)") token := strings.TrimPrefix(authHeader, "Bearer ") return token, token, nil } // Handle "Agent " format if strings.HasPrefix(authHeader, "Agent ") { + log.Print("Detected Agent ID format") agentIDValue := strings.TrimPrefix(authHeader, "Agent ") return agentIDValue, agentIDValue, nil } // Per MCP spec 7.1: Authorization header contains API key directly // Use the entire header value as both API key and agent/session ID + log.Print("Using plain API key format (MCP spec 7.1)") return authHeader, authHeader, nil } // ValidateAPIKey checks if the provided API key matches the expected key. // Returns true if they match, false otherwise. func ValidateAPIKey(provided, expected string) bool { + log.Printf("Validating API key: expected_configured=%t", expected != "") + if expected == "" { // No API key configured, authentication is disabled + log.Print("No API key configured, authentication disabled") return true } - return provided == expected + + matches := provided == expected + log.Printf("API key validation result: matches=%t", matches) + return matches } diff --git a/internal/auth/header_test.go b/internal/auth/header_test.go index c95c8cbd8..583cf2205 100644 --- a/internal/auth/header_test.go +++ b/internal/auth/header_test.go @@ -4,6 +4,53 @@ import ( "testing" ) +func TestSanitizeForLogging(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "Empty string", + input: "", + want: "", + }, + { + name: "Single character", + input: "a", + want: "...", + }, + { + name: "Four characters", + input: "abcd", + want: "...", + }, + { + name: "Five characters", + input: "abcde", + want: "abcd...", + }, + { + name: "Long string", + input: "my-secret-api-key-12345", + want: "my-s...", + }, + { + name: "API key with Bearer prefix", + input: "Bearer my-token-123", + want: "Bear...", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := sanitizeForLogging(tt.input); got != tt.want { + t.Errorf("sanitizeForLogging() = %v, want %v", got, tt.want) + } + }) + } +} + func TestParseAuthHeader(t *testing.T) { tests := []struct { name string