-
Notifications
You must be signed in to change notification settings - Fork 29
feat(cli): summary improvements over sensitive data #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+158
−51
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| package root | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "io" | ||
| "os" | ||
| "regexp" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/algolia/cli/pkg/telemetry" | ||
| ) | ||
|
|
||
| // runSummary is the JSON object written to stderr when DEBUG is set. | ||
| type runSummary struct { | ||
| Event string `json:"event"` | ||
| InvocationID string `json:"invocation_id"` | ||
| Command string `json:"command"` | ||
| Status string `json:"status"` | ||
| DurationMs int64 `json:"duration_ms"` | ||
| Error string `json:"error,omitempty"` | ||
| } | ||
|
|
||
| func shouldEmitRunSummary() bool { | ||
| return os.Getenv("DEBUG") != "" | ||
| } | ||
|
|
||
| // sensitiveFlagPattern matches flags that take a secret value; submatch 1 = flag name (e.g. --api-key= or -p ), submatch 2 = value. | ||
| var sensitiveFlagPattern = regexp.MustCompile( | ||
| `(--(?:api-key|application-id|admin-api-key)=)(\S+)|(--(?:api-key|application-id|admin-api-key)\s+)(\S+)|(-p\s+)(\S+)`) | ||
|
|
||
| // sensitiveValuePattern matches key/value pairs in error messages; submatch 1 = label, submatch 2 = value. | ||
| var sensitiveValuePattern = regexp.MustCompile( | ||
| `(?i)(api[_\s]?key|application[_\s]?id)\s*[:=]\s*([^\s]+)`) | ||
|
|
||
| // maskWithLast4 returns a masked string showing only the last 4 chars (e.g. ***c123). If len <= 4, returns ****. | ||
| func maskWithLast4(s string) string { | ||
| if len(s) <= 4 { | ||
| return "****" | ||
| } | ||
| return "***" + s[len(s)-4:] | ||
| } | ||
|
|
||
| // sanitizeRunSummaryCommand redacts sensitive flag values, keeping last 4 chars (e.g. --api-key=***c123). | ||
| func sanitizeRunSummaryCommand(cmd string) string { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO this is getting much too much just to get a summary of what was ran. |
||
| return sensitiveFlagPattern.ReplaceAllStringFunc(cmd, func(match string) string { | ||
| subs := sensitiveFlagPattern.FindStringSubmatch(match) | ||
| if len(subs) < 3 { | ||
| return match | ||
| } | ||
| for i := 1; i < len(subs); i += 2 { | ||
| if subs[i] != "" && i+1 < len(subs) && subs[i+1] != "" { | ||
| return subs[i] + maskWithLast4(subs[i+1]) | ||
| } | ||
| } | ||
| return match | ||
| }) | ||
| } | ||
|
|
||
| // sanitizeRunSummaryError redacts sensitive values in error messages, keeping last 4 chars (e.g. api_key: ***c123). | ||
| func sanitizeRunSummaryError(errMsg string) string { | ||
| s := sensitiveValuePattern.ReplaceAllStringFunc(errMsg, func(match string) string { | ||
| subs := sensitiveValuePattern.FindStringSubmatch(match) | ||
| if len(subs) >= 3 && subs[2] != "" { | ||
| return subs[1] + ": " + maskWithLast4(subs[2]) | ||
| } | ||
| return match | ||
| }) | ||
| if len(s) > 500 { | ||
| s = s[:497] + "..." | ||
| } | ||
| return s | ||
| } | ||
|
|
||
| func emitRunSummary(stderr io.Writer, ctx context.Context, cmd *cobra.Command, runErr error, duration time.Duration) { | ||
| meta := telemetry.GetEventMetadata(ctx) | ||
| invocationID := "" | ||
| if meta != nil { | ||
| invocationID = meta.InvocationID | ||
| } | ||
| commandPath := "" | ||
| if meta != nil && meta.CommandPath != "" { | ||
| commandPath = meta.CommandPath | ||
| } | ||
| if commandPath == "" && cmd != nil { | ||
| commandPath = cmd.CommandPath() | ||
| } | ||
| commandPath = sanitizeRunSummaryCommand(commandPath) | ||
| status := "ok" | ||
| errMsg := "" | ||
| if runErr != nil { | ||
| status = "error" | ||
| errMsg = sanitizeRunSummaryError(runErr.Error()) | ||
| } | ||
| s := runSummary{ | ||
| Event: "cli_run", | ||
| InvocationID: invocationID, | ||
| Command: commandPath, | ||
| Status: status, | ||
| DurationMs: duration.Milliseconds(), | ||
| Error: errMsg, | ||
| } | ||
| enc := json.NewEncoder(stderr) | ||
| enc.SetEscapeHTML(false) | ||
| _ = enc.Encode(s) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package root | ||
|
|
||
| import ( | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
| func TestSanitizeRunSummaryCommand(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| in string | ||
| want string | ||
| }{ | ||
| {"no secrets", "algolia indices list", "algolia indices list"}, | ||
| {"api-key equals", "algolia --api-key=abc123 indices list", "algolia --api-key=***c123 indices list"}, | ||
| {"application-id equals", "algolia --application-id=MYAPP indices list", "algolia --application-id=***YAPP indices list"}, | ||
| {"api-key space", "algolia --api-key abc123 indices list", "algolia --api-key ***c123 indices list"}, | ||
| {"profile short", "algolia -p myprofile indices list", "algolia -p ***file indices list"}, | ||
| {"short value", "algolia --api-key=ab indices list", "algolia --api-key=**** indices list"}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := sanitizeRunSummaryCommand(tt.in) | ||
| if got != tt.want { | ||
| t.Errorf("sanitizeRunSummaryCommand(%q) = %q, want %q", tt.in, got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestSanitizeRunSummaryError(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| in string | ||
| want string | ||
| }{ | ||
| {"no secrets", "connection refused", "connection refused"}, | ||
| {"api_key colon", "error: api_key: abc123def456", "error: api_key: ***f456"}, | ||
| {"application_id equals", "invalid application_id=MYAPP", "invalid application_id: ***YAPP"}, | ||
| {"truncate long", strings.Repeat("x", 600), strings.Repeat("x", 497) + "..."}, | ||
| } | ||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| got := sanitizeRunSummaryError(tt.in) | ||
| if got != tt.want { | ||
| t.Errorf("sanitizeRunSummaryError() = %q, want %q", got, tt.want) | ||
| } | ||
| }) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to hide
-p? It'sprofilefor most/all commands, might be useful to keep visibleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I couldn't find a command to see this specific behavior