Skip to content

Commit 3e01a97

Browse files
committed
fix: redact nested Pulsar resource configs
1 parent 747df24 commit 3e01a97

2 files changed

Lines changed: 105 additions & 6 deletions

File tree

pkg/mcp/pulsar_resources.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const (
8181
pulsarWorkerMetricsResourceURI = "pulsar://admin/v2/worker-stats/metrics"
8282
pulsarResourceJSONMIMEType = "application/json"
8383
pulsarResourceSummaryStringLimit = 50
84+
pulsarResourceSanitizeDepthLimit = 6
8485
pulsarResourceRedactedValue = "<redacted>"
8586
)
8687

@@ -3302,9 +3303,16 @@ func sanitizePulsarResourceStringMap(values map[string]string, limit int) map[st
33023303
}
33033304

33043305
func sanitizePulsarResourceAnyMap(values map[string]any, limit int) map[string]any {
3306+
return sanitizePulsarResourceAnyMapWithDepth(values, limit, pulsarResourceSanitizeDepthLimit)
3307+
}
3308+
3309+
func sanitizePulsarResourceAnyMapWithDepth(values map[string]any, limit int, depth int) map[string]any {
33053310
if len(values) == 0 || limit <= 0 {
33063311
return nil
33073312
}
3313+
if depth <= 0 {
3314+
return nil
3315+
}
33083316

33093317
keys := make([]string, 0, len(values))
33103318
for key := range values {
@@ -3321,22 +3329,27 @@ func sanitizePulsarResourceAnyMap(values map[string]any, limit int) map[string]a
33213329
sanitized[key] = pulsarResourceRedactedValue
33223330
continue
33233331
}
3324-
sanitized[key] = sanitizePulsarResourceAnyValue(values[key], 2)
3332+
sanitized[key] = sanitizePulsarResourceAnyValue(values[key], depth-1)
33253333
}
33263334
return sanitized
33273335
}
33283336

33293337
func sanitizePulsarResourceAnyValue(value any, depth int) any {
3330-
if depth <= 0 {
3331-
return value
3332-
}
3333-
33343338
switch typed := value.(type) {
33353339
case map[string]any:
3336-
return sanitizePulsarResourceAnyMap(typed, pulsarResourceSummaryStringLimit)
3340+
if depth <= 0 {
3341+
return pulsarResourceRedactedValue
3342+
}
3343+
return sanitizePulsarResourceAnyMapWithDepth(typed, pulsarResourceSummaryStringLimit, depth)
33373344
case map[string]string:
3345+
if depth <= 0 {
3346+
return pulsarResourceRedactedValue
3347+
}
33383348
return sanitizePulsarResourceStringMap(typed, pulsarResourceSummaryStringLimit)
33393349
case []any:
3350+
if depth <= 0 {
3351+
return pulsarResourceRedactedValue
3352+
}
33403353
values := typed
33413354
if len(values) > pulsarResourceSummaryStringLimit {
33423355
values = values[:pulsarResourceSummaryStringLimit]
@@ -3347,6 +3360,9 @@ func sanitizePulsarResourceAnyValue(value any, depth int) any {
33473360
}
33483361
return sanitized
33493362
case []string:
3363+
if depth <= 0 {
3364+
return pulsarResourceRedactedValue
3365+
}
33503366
values, _ := limitStringSlice(typed, pulsarResourceSummaryStringLimit)
33513367
return values
33523368
default:

pkg/mcp/pulsar_resources_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,89 @@ func TestPulsarAddResourcesFeatureGate(t *testing.T) {
284284
})
285285
}
286286

287+
func TestSanitizePulsarResourceAnyMapRedactsNestedValues(t *testing.T) {
288+
t.Parallel()
289+
290+
longSlice := make([]any, pulsarResourceSummaryStringLimit+1)
291+
for i := range longSlice {
292+
longSlice[i] = map[string]any{"index": i}
293+
}
294+
tooDeep := map[string]any{"password": "secret-over-budget"}
295+
for i := 0; i <= pulsarResourceSanitizeDepthLimit; i++ {
296+
tooDeep = map[string]any{fmt.Sprintf("level-%02d", i): tooDeep}
297+
}
298+
299+
values := map[string]any{
300+
"owner": "team-a",
301+
"nested": map[string]any{
302+
"database": map[string]any{
303+
"connection": map[string]any{
304+
"password": "secret-nested-password",
305+
"safe": "visible",
306+
},
307+
"token": "secret-nested-token",
308+
},
309+
},
310+
"items": []any{
311+
map[string]any{
312+
"password": "secret-array-password",
313+
"safe": "array-visible",
314+
},
315+
map[string]any{
316+
"metadata": map[string]any{
317+
"privateKey": "secret-array-private-key",
318+
},
319+
},
320+
},
321+
"stringMap": map[string]string{
322+
"clientKey": "secret-client-key",
323+
"owner": "team-a",
324+
},
325+
"longSlice": longSlice,
326+
"tooDeep": tooDeep,
327+
}
328+
329+
sanitized := sanitizePulsarResourceAnyMap(values, pulsarResourceSummaryStringLimit)
330+
payload, err := json.Marshal(sanitized)
331+
require.NoError(t, err)
332+
assert.NotContains(t, string(payload), "secret-nested-password")
333+
assert.NotContains(t, string(payload), "secret-nested-token")
334+
assert.NotContains(t, string(payload), "secret-array-password")
335+
assert.NotContains(t, string(payload), "secret-array-private-key")
336+
assert.NotContains(t, string(payload), "secret-client-key")
337+
assert.NotContains(t, string(payload), "secret-over-budget")
338+
339+
nested := sanitized["nested"].(map[string]any)
340+
database := nested["database"].(map[string]any)
341+
connection := database["connection"].(map[string]any)
342+
assert.Equal(t, pulsarResourceRedactedValue, connection["password"])
343+
assert.Equal(t, "visible", connection["safe"])
344+
assert.Equal(t, pulsarResourceRedactedValue, database["token"])
345+
346+
items := sanitized["items"].([]any)
347+
firstItem := items[0].(map[string]any)
348+
assert.Equal(t, pulsarResourceRedactedValue, firstItem["password"])
349+
assert.Equal(t, "array-visible", firstItem["safe"])
350+
secondItem := items[1].(map[string]any)
351+
metadata := secondItem["metadata"].(map[string]any)
352+
assert.Equal(t, pulsarResourceRedactedValue, metadata["privateKey"])
353+
354+
stringMap := sanitized["stringMap"].(map[string]string)
355+
assert.Equal(t, pulsarResourceRedactedValue, stringMap["clientKey"])
356+
assert.Equal(t, "team-a", stringMap["owner"])
357+
358+
limitedSlice := sanitized["longSlice"].([]any)
359+
assert.Len(t, limitedSlice, pulsarResourceSummaryStringLimit)
360+
361+
limitedMap := make(map[string]any, pulsarResourceSummaryStringLimit+1)
362+
for i := 0; i <= pulsarResourceSummaryStringLimit; i++ {
363+
limitedMap[fmt.Sprintf("key-%02d", i)] = i
364+
}
365+
sanitizedLimitedMap := sanitizePulsarResourceAnyMap(limitedMap, pulsarResourceSummaryStringLimit)
366+
assert.Len(t, sanitizedLimitedMap, pulsarResourceSummaryStringLimit)
367+
assert.NotContains(t, sanitizedLimitedMap, fmt.Sprintf("key-%02d", pulsarResourceSummaryStringLimit))
368+
}
369+
287370
func TestPulsarContextResourceReadRedactsSecrets(t *testing.T) {
288371
t.Parallel()
289372

0 commit comments

Comments
 (0)