Skip to content

Commit

Permalink
Adds sanitization of request log data on output (#1280)
Browse files Browse the repository at this point in the history
* Adds sanitization of request log data on output

* Appease goimports

* appease go-mod-tidy
  • Loading branch information
fhats-stripe authored Dec 19, 2024
1 parent 13a948c commit 549b46d
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
)

require (
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/go-git/go-git/v5 v5.12.0
github.com/hashicorp/go-hclog v1.2.2
github.com/hashicorp/go-plugin v1.4.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/briandowns/spinner v1.19.0 h1:s8aq38H+Qju89yhp89b4iIiMzMm8YN3p6vGpwyh/a8E=
Expand Down
28 changes: 28 additions & 0 deletions pkg/cmd/logs/tail.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"os"
"os/signal"
"reflect"
"regexp"
"strings"
"syscall"
"time"

"github.com/acarl005/stripansi"
"github.com/briandowns/spinner"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -28,6 +30,8 @@ import (

const outputFormatJSON = "JSON"

var newlineRegex = regexp.MustCompile("[\r\n]")

// TailCmd wraps the configuration for the tail command
type TailCmd struct {
apiBaseURL string
Expand Down Expand Up @@ -289,6 +293,8 @@ func createVisitor(logger *log.Logger, format string) *websocket.Visitor {
return fmt.Errorf("VisitData received unexpected type for DataElement, got %T expected %T", de, logtailing.EventPayload{})
}

sanitizePayload(&log)

if strings.ToUpper(format) == outputFormatJSON {
fmt.Println(ansi.ColorizeJSON(de.Marshaled, false, os.Stdout))
return nil
Expand Down Expand Up @@ -337,3 +343,25 @@ func urlForRequestID(payload *logtailing.EventPayload) string {

return fmt.Sprintf("https://dashboard.stripe.com%s/logs/%s", maybeTest, payload.RequestID)
}

func sanitize(str string) string {
withoutAnsi := stripansi.Strip(str)
withoutNewlines := newlineRegex.ReplaceAllLiteralString(withoutAnsi, "")
return strings.TrimSpace(withoutNewlines)
}

func sanitizePayload(payload *logtailing.EventPayload) {
payload.Error.Charge = sanitize(payload.Error.Charge)
payload.Error.Code = sanitize(payload.Error.Code)
payload.Error.DeclineCode = sanitize(payload.Error.DeclineCode)
payload.Error.ErrorInsight = sanitize(payload.Error.ErrorInsight)
payload.Error.Message = sanitize(payload.Error.Message)
payload.Error.Param = sanitize(payload.Error.Param)
payload.Error.Type = sanitize(payload.Error.Type)

payload.Method = sanitize(payload.Method)

payload.RequestID = sanitize(payload.RequestID)

payload.URL = sanitize(payload.URL)
}
123 changes: 123 additions & 0 deletions pkg/cmd/logs/tail_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package logs

import (
"fmt"
"reflect"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/stripe/stripe-cli/pkg/logtailing"
)

func hasZeroValueString(v reflect.Value) bool {
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
if hasZeroValueString(v.Field(i)) {
return true
}
}
return false
case reflect.String:
return v.IsZero()
default:
return false
}
}

func containsZeroValueStrings(x interface{}) bool {
v := reflect.ValueOf(x)

// If it's a pointer, dereference it
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

if !v.IsValid() {
return true
}

return hasZeroValueString(v)
}

func TestSanitize(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "does not change basic strings",
input: "GET",
expected: "GET",
},
{
name: "removes ansi escape codes",
input: "\x0d\x0a\x1b[90mvery cool\x0d\x0a\x1b[32m and very legal",
expected: "very cool and very legal",
},
{
name: "removes newlines",
input: "\x0d\x0a\x1b[90mvery cool",
expected: "very cool",
},
{
name: "removes both ansi escape codes and newlines",
input: "\x0d\x0a\x1b[90ma horse\r\n a dog\n a cat\x0d\x0a\x1b[32m and a bird",
expected: "a horse a dog a cat and a bird",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := sanitize(tt.input)
assert.Equal(t, tt.expected, actual)
})
}
}

func TestSanitizePayload(t *testing.T) {
withAnsi := func(s string) string {
return fmt.Sprintf("\x1b[90m%s\x1b[0m", s)
}

payload := logtailing.EventPayload{
Error: logtailing.RedactedError{
Charge: withAnsi("ch_123"),
Code: withAnsi("invlaid_argument"),
DeclineCode: withAnsi("card_declined"),
ErrorInsight: withAnsi("make fewer errors"),
Message: withAnsi("an error occurred"),
Param: withAnsi("card"),
Type: withAnsi("invalid_request"),
},
Method: withAnsi("POST"),
RequestID: withAnsi("req_123"),
URL: withAnsi("https://example.com"),
}

expected := logtailing.EventPayload{
Error: logtailing.RedactedError{
Charge: "ch_123",
Code: "invlaid_argument",
DeclineCode: "card_declined",
ErrorInsight: "make fewer errors",
Message: "an error occurred",
Param: "card",
Type: "invalid_request",
},
Method: "POST",
RequestID: "req_123",
URL: "https://example.com",
}

// Ensures that we're testing/covering the entire payload in case
// any new fields are added
require.Equal(t, containsZeroValueStrings(payload), false)

sanitizePayload(&payload)

assert.Equal(t, expected, payload)
}

0 comments on commit 549b46d

Please sign in to comment.