feat(audit): sanitize stored upstream responses

This commit is contained in:
2026-09-21 11:10:01 +03:30
parent 22d67c7c36
commit 14df432175
5 changed files with 145 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ const (
StatusUpstreamRejected Status = "upstream_rejected"
StatusUpstreamTimeout Status = "upstream_timeout"
StatusUpstreamUnavailable Status = "upstream_unavailable"
StatusUpstreamInvalid Status = "upstream_invalid_response"
StatusInternalError Status = "internal_error"
)

View File

@@ -0,0 +1,48 @@
package audit
import (
"encoding/json"
"strings"
)
func SanitizeResponse(raw []byte, maskedCard string) []byte {
if len(raw) == 0 {
return nil
}
var value any
if err := json.Unmarshal(raw, &value); err != nil {
return nil
}
if maskedCard == "" {
maskedCard = "[redacted]"
}
redactCardFields(value, maskedCard)
sanitized, err := json.Marshal(value)
if err != nil {
return nil
}
return sanitized
}
func redactCardFields(value any, maskedCard string) {
switch current := value.(type) {
case map[string]any:
for key, child := range current {
if strings.EqualFold(key, "card") {
current[key] = maskedCard
continue
}
redactCardFields(child, maskedCard)
}
case []any:
for _, child := range current {
redactCardFields(child, maskedCard)
}
}
}

View File

@@ -0,0 +1,56 @@
package audit
import (
"encoding/json"
"testing"
)
func TestSanitizeResponse(t *testing.T) {
raw := []byte(`{
"data": {
"card": "6037997512345678",
"iban": "IR820540102680020817909002"
},
"meta": {"isSuccess": true}
}`)
got := SanitizeResponse(raw, "603799******5678")
if len(got) == 0 {
t.Fatal("SanitizeResponse() returned an empty response")
}
var decoded map[string]any
if err := json.Unmarshal(got, &decoded); err != nil {
t.Fatalf("unmarshal sanitized response: %v", err)
}
data, ok := decoded["data"].(map[string]any)
if !ok {
t.Fatal("sanitized response does not contain data object")
}
if gotCard := data["card"]; gotCard != "603799******5678" {
t.Errorf("sanitized card = %v, want masked card", gotCard)
}
if gotIBAN := data["iban"]; gotIBAN != "IR820540102680020817909002" {
t.Errorf("sanitized iban = %v, want original iban", gotIBAN)
}
}
func TestSanitizeResponseRejectsInvalidJSON(t *testing.T) {
if got := SanitizeResponse([]byte(`not-json`), "603799******5678"); got != nil {
t.Errorf("SanitizeResponse() = %q, want nil", got)
}
}
func TestSanitizeResponseRedactsCardWhenMaskIsEmpty(t *testing.T) {
got := SanitizeResponse(
[]byte(`{"card":"6037997512345678"}`),
"",
)
if string(got) != `{"card":"[redacted]"}` {
t.Errorf("SanitizeResponse() = %s, want redacted card", got)
}
}