Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions internal/config/validation_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ var (

// gatewayVersion stores the version string to include in error messages
gatewayVersion = "dev"

// logSchema is the debug logger for schema validation
logSchema = logger.New("config:validation_schema")
)
Expand All @@ -39,7 +39,7 @@ func SetGatewayVersion(version string) {
// regex patterns that use negative lookahead (not supported in JSON Schema Draft 7)
func fetchAndFixSchema(url string) ([]byte, error) {
logSchema.Printf("Fetching schema from URL: %s", url)

client := &http.Client{
Timeout: 10 * time.Second,
}
Expand All @@ -55,7 +55,7 @@ func fetchAndFixSchema(url string) ([]byte, error) {
logSchema.Printf("Schema fetch returned non-OK status: %d", resp.StatusCode)
return nil, fmt.Errorf("failed to fetch schema: HTTP %d", resp.StatusCode)
}

logSchema.Print("Schema fetched successfully, applying fixes")

schemaBytes, err := io.ReadAll(resp.Body)
Expand Down Expand Up @@ -121,7 +121,7 @@ func fetchAndFixSchema(url string) ([]byte, error) {
// validateJSONSchema validates the raw JSON configuration against the JSON schema
func validateJSONSchema(data []byte) error {
logSchema.Printf("Starting JSON schema validation: data_size=%d bytes", len(data))

// Fetch the schema from the remote URL (source of truth)
schemaURL := "https://raw.githubusercontent.com/githubnext/gh-aw/main/docs/public/schemas/mcp-gateway-config.schema.json"
schemaJSON, err := fetchAndFixSchema(schemaURL)
Expand Down Expand Up @@ -295,7 +295,7 @@ func formatErrorContext(ve *jsonschema.ValidationError, prefix string) string {
// This provides additional validation beyond the JSON schema validation
func validateStringPatterns(stdinCfg *StdinConfig) error {
logSchema.Printf("Validating string patterns: server_count=%d", len(stdinCfg.MCPServers))

// Validate server configurations
for name, server := range stdinCfg.MCPServers {
jsonPath := fmt.Sprintf("mcpServers.%s", name)
Expand Down
10 changes: 5 additions & 5 deletions internal/logger/rpc_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,13 @@ func TestTruncateAndSanitize(t *testing.T) {
name: "sanitize secrets - GitHub token",
payload: "token: ghp_1234567890abcdefghijklmnopqrstuvwxyz",
maxLength: 100,
want: "token: [REDACTED]",
want: "token=[REDACTED]",
},
{
name: "sanitize and truncate",
payload: "auth bearer ghp_1234567890abcdefghijklmnopqrstuvwxyz " + strings.Repeat("x", 100),
maxLength: 50,
want: "auth bearer [REDACTED] " + strings.Repeat("x", 23) + "...",
want: "auth bearer [REDACTED] " + strings.Repeat("x", 27) + "...",
},
}

Expand Down Expand Up @@ -308,18 +308,18 @@ func TestExtractEssentialFields(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractEssentialFields([]byte(tt.payload))

if tt.want == nil {
assert.Nil(t, result)
return
}

require.NotNil(t, result)
assert.Equal(t, tt.want["jsonrpc"], result["jsonrpc"])
assert.Equal(t, tt.want["method"], result["method"])
assert.Equal(t, tt.want["id"], result["id"])
assert.Equal(t, tt.want["error"], result["error"])

// Special handling for params_keys since order may vary
if expectedKeys, ok := tt.want["params_keys"].([]string); ok {
actualKeys, ok := result["params_keys"].([]string)
Expand Down
108 changes: 0 additions & 108 deletions internal/logger/rpc_logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,114 +11,6 @@ import (
"github.com/stretchr/testify/require"
)

func TestTruncateAndSanitize(t *testing.T) {
tests := []struct {
name string
input string
maxLength int
wantLen int // Expected length (may be less due to sanitization)
wantRedacted bool
}{
{
name: "short message without secrets",
input: "Hello, world!",
maxLength: 50,
wantLen: 13,
wantRedacted: false,
},
{
name: "long message gets truncated",
input: `{"method":"test","data":"` + strings.Repeat("x", 200) + `"}`,
maxLength: 100,
wantLen: 103, // 100 + "..."
wantRedacted: false,
},
{
name: "message with token gets sanitized",
input: "Authorization: ghp_1234567890123456789012345678901234567890",
maxLength: 150,
wantLen: -1, // Variable due to redaction
wantRedacted: true,
},
{
name: "message with password gets sanitized",
input: "password=supersecretpassword123",
maxLength: 150,
wantLen: -1, // Variable due to redaction
wantRedacted: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := truncateAndSanitize(tt.input, tt.maxLength)

if tt.wantRedacted {
if !strings.Contains(result, "[REDACTED]") {
t.Errorf("Expected result to contain [REDACTED], got: %s", result)
}
} else {
if tt.wantLen > 0 && len(result) != tt.wantLen {
t.Errorf("Expected length %d, got %d: %s", tt.wantLen, len(result), result)
}
}

// Ensure result is not longer than maxLength + 3 (for "...")
if !tt.wantRedacted && len(result) > tt.maxLength+3 {
t.Errorf("Result too long: %d > %d", len(result), tt.maxLength+3)
}
})
}
}

func TestExtractEssentialFields(t *testing.T) {
tests := []struct {
name string
payload string
wantKeys []string
}{
{
name: "JSON-RPC request",
payload: `{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}`,
wantKeys: []string{"jsonrpc", "id", "method", "params_keys"},
},
{
name: "JSON-RPC response with result",
payload: `{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}`,
wantKeys: []string{"jsonrpc", "id"},
},
{
name: "JSON-RPC response with error",
payload: `{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid request"}}`,
wantKeys: []string{"jsonrpc", "id", "error"},
},
{
name: "invalid JSON",
payload: `{invalid json}`,
wantKeys: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := extractEssentialFields([]byte(tt.payload))

if tt.wantKeys == nil {
assert.Nil(t, result, "Expected nil result for invalid JSON")
return
}

require.NotNil(t, result, "Expected result map, got nil")

for _, key := range tt.wantKeys {
if _, ok := result[key]; !ok {
t.Errorf("Expected key %s not found in result: %v", key, result)
}
}
})
}
}

func TestFormatRPCMessage(t *testing.T) {
tests := []struct {
name string
Expand Down