Skip to content

Commit b75f471

Browse files
committed
Implement secure logging for environment variables and add checksum verification tests
- Introduced LogConfig function to log environment variable checksums without exposing sensitive values. - Added TestLogConfig and TestLogConfigConsistency to validate logging behavior and checksum consistency. - Updated existing tests with English names for clarity.
1 parent 00ff75d commit b75f471

File tree

3 files changed

+158
-2
lines changed

3 files changed

+158
-2
lines changed

config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package main
22

33
import (
4+
"crypto/sha256"
5+
"encoding/hex"
46
"encoding/json"
57
"fmt"
8+
"log/slog"
69
"os"
710
"path/filepath"
811

@@ -60,3 +63,18 @@ func ConvertToMCPClientConfig(serverCfg ServerConfig) *MCPClientConfig {
6063
Env: serverCfg.Env,
6164
}
6265
}
66+
67+
// LogSafeEnvChecksum calculates the SHA-256 checksum of environment variable values
68+
// without exposing the actual values in logs
69+
func LogConfig(logger *slog.Logger, serverName string, env map[string]string) {
70+
for key, value := range env {
71+
// Calculate SHA-256 checksum of the value
72+
hash := sha256.Sum256([]byte(value))
73+
checksum := hex.EncodeToString(hash[:])
74+
// Log the key and value checksum (not the actual value)
75+
logger.Debug("Environment variable checksum",
76+
"server", serverName,
77+
"key", key,
78+
"value_checksum", checksum[:8]) // Only log first 8 chars of checksum for brevity
79+
}
80+
}

config_test.go

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"encoding/json"
5+
"log/slog"
56
"os"
67
"path/filepath"
78
"reflect"
9+
"strings"
810
"testing"
911
)
1012

@@ -156,7 +158,7 @@ func TestConvertToMCPClientConfig(t *testing.T) {
156158
want *MCPClientConfig
157159
}{
158160
{
159-
name: "基本的な変換",
161+
name: "Basic conversion",
160162
serverCfg: ServerConfig{
161163
Command: "test-command",
162164
Args: []string{"arg1", "arg2"},
@@ -169,7 +171,7 @@ func TestConvertToMCPClientConfig(t *testing.T) {
169171
},
170172
},
171173
{
172-
name: "空の値を持つ設定",
174+
name: "Empty values",
173175
serverCfg: ServerConfig{
174176
Command: "",
175177
Args: []string{},
@@ -216,3 +218,134 @@ func TestMCPClientConfigSerialization(t *testing.T) {
216218
t.Errorf("Data does not match after JSON round-trip. Original: %v, Result: %v", original, jsonDeserialized)
217219
}
218220
}
221+
222+
// TestLogConfig tests secure logging functionality for environment variables
223+
func TestLogConfig(t *testing.T) {
224+
// Create test environment variables map
225+
testEnv := map[string]string{
226+
"API_KEY": "secret-api-key-123",
227+
"DATABASE_URL": "postgres://user:password@localhost:5432/db",
228+
"DEBUG": "true",
229+
}
230+
231+
// Prepare buffer to capture log output
232+
var logBuffer strings.Builder
233+
testHandler := slog.NewTextHandler(&logBuffer, &slog.HandlerOptions{
234+
Level: slog.LevelDebug,
235+
})
236+
testLogger := slog.New(testHandler)
237+
238+
// Execute the function under test
239+
LogConfig(testLogger, "test-server", testEnv)
240+
241+
// Verify log output
242+
logOutput := logBuffer.String()
243+
244+
// Verify each environment variable key is present in logs
245+
for key := range testEnv {
246+
if !strings.Contains(logOutput, key) {
247+
t.Errorf("Log output doesn't contain key %s", key)
248+
}
249+
}
250+
251+
// Verify actual values (sensitive info) are not present in logs
252+
for _, value := range testEnv {
253+
if strings.Contains(logOutput, value) {
254+
t.Errorf("Log output contains raw sensitive value: %s", value)
255+
}
256+
}
257+
258+
// Verify checksum format (generic check since implementation dependent)
259+
if !strings.Contains(logOutput, "value_checksum=") {
260+
t.Errorf("Log output doesn't contain checksums")
261+
}
262+
263+
// Verify server name is correctly included in logs
264+
if !strings.Contains(logOutput, "test-server") {
265+
t.Errorf("Log output doesn't contain server name")
266+
}
267+
}
268+
269+
// TestLogConfigConsistency tests the consistency of checksum calculation
270+
func TestLogConfigConsistency(t *testing.T) {
271+
// Capture and compare multiple log outputs for the same value
272+
testValue := "very-secret-password"
273+
testEnv := map[string]string{
274+
"TEST_SECRET": testValue,
275+
}
276+
277+
// First log output
278+
var logBuffer1 strings.Builder
279+
testHandler1 := slog.NewTextHandler(&logBuffer1, &slog.HandlerOptions{
280+
Level: slog.LevelDebug,
281+
})
282+
testLogger1 := slog.New(testHandler1)
283+
LogConfig(testLogger1, "test-server", testEnv)
284+
logOutput1 := logBuffer1.String()
285+
286+
// Second log output
287+
var logBuffer2 strings.Builder
288+
testHandler2 := slog.NewTextHandler(&logBuffer2, &slog.HandlerOptions{
289+
Level: slog.LevelDebug,
290+
})
291+
testLogger2 := slog.New(testHandler2)
292+
LogConfig(testLogger2, "test-server", testEnv)
293+
logOutput2 := logBuffer2.String()
294+
295+
// Extract checksum from log outputs
296+
checksum1 := extractChecksum(logOutput1)
297+
checksum2 := extractChecksum(logOutput2)
298+
299+
// Verify same checksum is generated for same value
300+
if checksum1 == "" || checksum2 == "" {
301+
t.Errorf("Failed to extract checksums from log output")
302+
} else if checksum1 != checksum2 {
303+
t.Errorf("Checksum calculation is not consistent: %s != %s", checksum1, checksum2)
304+
}
305+
306+
// Verify different checksum is generated when value changes
307+
testEnvModified := map[string]string{
308+
"TEST_SECRET": testValue + "-modified",
309+
}
310+
311+
var logBuffer3 strings.Builder
312+
testHandler3 := slog.NewTextHandler(&logBuffer3, &slog.HandlerOptions{
313+
Level: slog.LevelDebug,
314+
})
315+
testLogger3 := slog.New(testHandler3)
316+
LogConfig(testLogger3, "test-server", testEnvModified)
317+
logOutput3 := logBuffer3.String()
318+
319+
// Extract checksum from modified value log
320+
checksum3 := extractChecksum(logOutput3)
321+
322+
// Verify different values produce different checksums
323+
if checksum1 == "" || checksum3 == "" {
324+
t.Errorf("Failed to extract checksums from log output")
325+
} else if checksum1 == checksum3 {
326+
t.Errorf("Different values produced the same checksum: %s", checksum1)
327+
}
328+
}
329+
330+
// extractChecksum extracts the checksum value from log output
331+
func extractChecksum(logOutput string) string {
332+
// Find the value_checksum field in the log output
333+
checksumIndex := strings.Index(logOutput, "value_checksum=")
334+
if checksumIndex == -1 {
335+
return ""
336+
}
337+
338+
// Extract the checksum value (assuming it's 8 chars as specified in the implementation)
339+
checksumStart := checksumIndex + len("value_checksum=")
340+
if checksumStart >= len(logOutput) {
341+
return ""
342+
}
343+
344+
// Find the end of the checksum (either space, newline or end of string)
345+
checksumEnd := checksumStart + 8
346+
if checksumEnd > len(logOutput) {
347+
checksumEnd = len(logOutput)
348+
}
349+
350+
return logOutput[checksumStart:checksumEnd]
351+
}

main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ func main() {
8686
}
8787
tempMcpClients[name] = client
8888
logger.Info("MCP Server initialized successfully", "server_name", name)
89+
90+
// Log environment variables checksums if in debug mode
91+
if *debug {
92+
LogConfig(logger, name, serverCfg.Env)
93+
}
8994
}
9095

9196
// Update server's mcpClients map atomically with initMu lock

0 commit comments

Comments
 (0)