| 1 | package secrets |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "encoding/json" |
| 6 | "errors" |
| 7 | ) |
| 8 | |
| 9 | // RedactJSON scrubs diagnostic text without applying text replacements to JSON |
| 10 | // syntax or numeric counters. UseNumber preserves sequence numbers exactly. |
| 11 | func RedactJSON(data []byte) ([]byte, error) { |
| 12 | if !json.Valid(data) { |
| 13 | return nil, errors.New("invalid diagnostic JSON") |
| 14 | } |
| 15 | var value any |
| 16 | decoder := json.NewDecoder(bytes.NewReader(data)) |
| 17 | decoder.UseNumber() |
| 18 | if err := decoder.Decode(&value); err != nil { |
| 19 | return nil, err |
| 20 | } |
| 21 | return json.Marshal(redactJSONValue(value)) |
| 22 | } |
| 23 | |
| 24 | func redactJSONValue(value any) any { |
| 25 | switch v := value.(type) { |
| 26 | case string: |
| 27 | return Redact(v) |
| 28 | case []any: |
| 29 | for i := range v { |
| 30 | v[i] = redactJSONValue(v[i]) |
| 31 | } |
| 32 | case map[string]any: |
| 33 | for key, child := range v { |
| 34 | if text, ok := child.(string); ok && credentialTextKeySensitive(key) && text != "" { |
| 35 | v[key] = redactedValue |
| 36 | } else { |
| 37 | v[key] = redactJSONValue(child) |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | return value |
| 42 | } |
| 43 |