Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 67cfcb5

Browse files
gossionclaude
andauthored
Sanitize CLI command in telemetry to prevent secret leakage (#335)
Strip flags and arguments from CLI commands before passing to TrackToolInvocation, keeping only the "az <group> <subcommand>" prefix. This prevents secrets (--client-secret, tokens) from leaking into OTLP/Application Insights and reduces telemetry cardinality. Co-authored-by: Claude <[email protected]>
1 parent b8caecf commit 67cfcb5

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

internal/components/azapi/handler.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package azapi
33
import (
44
"context"
55
"fmt"
6+
"strings"
67
"time"
78

89
"github.com/Azure/aks-mcp/internal/config"
@@ -11,6 +12,21 @@ import (
1112
"github.com/mark3labs/mcp-go/mcp"
1213
)
1314

15+
// sanitizeCliCommand extracts only the "az <group> <subcommand>" prefix from a
16+
// CLI command, stripping all flags and arguments. This prevents secrets and
17+
// high-cardinality values from leaking into telemetry.
18+
func sanitizeCliCommand(cmd string) string {
19+
tokens := strings.Fields(cmd)
20+
var kept []string
21+
for _, t := range tokens {
22+
if strings.HasPrefix(t, "-") {
23+
break
24+
}
25+
kept = append(kept, t)
26+
}
27+
return strings.Join(kept, " ")
28+
}
29+
1430
func AzApiHandler(azClient azcli.Client, cfg *config.ConfigData) func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
1531
return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
1632
args, ok := req.Params.Arguments.(map[string]interface{})
@@ -48,7 +64,7 @@ func AzApiHandler(azClient azcli.Client, cfg *config.ConfigData) func(ctx contex
4864
errMsg := fmt.Sprintf("failed to execute Azure CLI command: %v", err)
4965
logger.Errorf("AzApiHandler: %s", errMsg)
5066
if cfg.TelemetryService != nil {
51-
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, cliCommand, false)
67+
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, sanitizeCliCommand(cliCommand), false)
5268
}
5369
return mcp.NewToolResultError(errMsg), nil
5470
}
@@ -57,15 +73,15 @@ func AzApiHandler(azClient azcli.Client, cfg *config.ConfigData) func(ctx contex
5773
errMsg := fmt.Sprintf("Azure CLI command failed: %s", result.Error)
5874
logger.Errorf("AzApiHandler: %s", errMsg)
5975
if cfg.TelemetryService != nil {
60-
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, cliCommand, false)
76+
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, sanitizeCliCommand(cliCommand), false)
6177
}
6278
return mcp.NewToolResultError(errMsg), nil
6379
}
6480

6581
logger.Debugf("AzApiHandler: Command completed successfully")
6682

6783
if cfg.TelemetryService != nil {
68-
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, cliCommand, true)
84+
cfg.TelemetryService.TrackToolInvocation(ctx, req.Params.Name, sanitizeCliCommand(cliCommand), true)
6985
}
7086

7187
return mcp.NewToolResultText(string(result.Output)), nil

internal/components/azapi/handler_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,51 @@ func TestAzApiHandler_NilTelemetryService(t *testing.T) {
296296
t.Fatal("expected success result")
297297
}
298298
}
299+
300+
func TestSanitizeCliCommand(t *testing.T) {
301+
tests := []struct {
302+
name string
303+
input string
304+
expected string
305+
}{
306+
{
307+
name: "simple command without flags",
308+
input: "az group list",
309+
expected: "az group list",
310+
},
311+
{
312+
name: "command with flags",
313+
input: "az aks show --name foo --resource-group bar",
314+
expected: "az aks show",
315+
},
316+
{
317+
name: "command with secrets",
318+
input: "az login --service-principal --client-secret xxx",
319+
expected: "az login",
320+
},
321+
{
322+
name: "deep subcommand with flags",
323+
input: "az network vnet subnet show --name sub1 --vnet-name vnet1",
324+
expected: "az network vnet subnet show",
325+
},
326+
{
327+
name: "empty string",
328+
input: "",
329+
expected: "",
330+
},
331+
{
332+
name: "command with short flags",
333+
input: "az group list -o table",
334+
expected: "az group list",
335+
},
336+
}
337+
338+
for _, tc := range tests {
339+
t.Run(tc.name, func(t *testing.T) {
340+
got := sanitizeCliCommand(tc.input)
341+
if got != tc.expected {
342+
t.Errorf("sanitizeCliCommand(%q) = %q, want %q", tc.input, got, tc.expected)
343+
}
344+
})
345+
}
346+
}

0 commit comments

Comments
 (0)