Fix the five library adoption review findings
This commit is contained in:
123
internal/core/redaction/redaction.go
Normal file
123
internal/core/redaction/redaction.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Package redaction sanitizes diagnostic representations without modifying domain data.
|
||||
package redaction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Redactor replaces configured values in a single pass, longest matches first.
|
||||
// Its zero value is usable and leaves strings unchanged.
|
||||
type Redactor struct{ replacer *strings.Replacer }
|
||||
|
||||
func New(secrets []string) Redactor {
|
||||
values := map[string]bool{}
|
||||
for _, secret := range secrets {
|
||||
if secret == "" {
|
||||
continue
|
||||
}
|
||||
values[secret] = true
|
||||
// Provider content may itself be serialized JSON inside a string field.
|
||||
encoded, _ := json.Marshal(secret)
|
||||
values[string(encoded[1:len(encoded)-1])] = true
|
||||
}
|
||||
keys := make([]string, 0, len(values))
|
||||
for value := range values {
|
||||
keys = append(keys, value)
|
||||
}
|
||||
sort.Slice(keys, func(i, j int) bool {
|
||||
if len(keys[i]) == len(keys[j]) {
|
||||
return keys[i] < keys[j]
|
||||
}
|
||||
return len(keys[i]) > len(keys[j])
|
||||
})
|
||||
pairs := make([]string, 0, 2*len(keys))
|
||||
for _, key := range keys {
|
||||
pairs = append(pairs, key, "[REDACTED]")
|
||||
}
|
||||
return Redactor{replacer: strings.NewReplacer(pairs...)}
|
||||
}
|
||||
|
||||
func (r Redactor) String(value string) string {
|
||||
if r.replacer == nil {
|
||||
return value
|
||||
}
|
||||
return r.replacer.Replace(value)
|
||||
}
|
||||
|
||||
// JSON serializes first, then redacts only decoded string values and keys.
|
||||
// Numbers retain their exact representation. Failure never returns original data.
|
||||
func (r Redactor) JSON(value any) ([]byte, error) {
|
||||
raw, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("serialize diagnostic: %s", r.String(err.Error()))
|
||||
}
|
||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||
decoder.UseNumber()
|
||||
var decoded any
|
||||
if err := decoder.Decode(&decoded); err != nil {
|
||||
return nil, fmt.Errorf("decode diagnostic: %s", r.String(err.Error()))
|
||||
}
|
||||
safe, err := r.walk(decoded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.MarshalIndent(safe, "", " ")
|
||||
}
|
||||
|
||||
func (r Redactor) walk(value any) (any, error) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return r.String(v), nil
|
||||
case []any:
|
||||
for i := range v {
|
||||
var err error
|
||||
v[i], err = r.walk(v[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
case map[string]any:
|
||||
safe := make(map[string]any, len(v))
|
||||
for key, item := range v {
|
||||
safeKey := r.String(key)
|
||||
if _, exists := safe[safeKey]; exists {
|
||||
return nil, fmt.Errorf("redaction would produce duplicate object keys")
|
||||
}
|
||||
safeItem, err := r.walk(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
safe[safeKey] = safeItem
|
||||
}
|
||||
return safe, nil
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// YAML preserves scalar types while sanitizing string nodes, including map keys.
|
||||
func (r Redactor) YAML(raw []byte) ([]byte, error) {
|
||||
var node yaml.Node
|
||||
if err := yaml.Unmarshal(raw, &node); err != nil {
|
||||
return nil, fmt.Errorf("decode diagnostic YAML: %s", r.String(err.Error()))
|
||||
}
|
||||
var visit func(*yaml.Node)
|
||||
visit = func(n *yaml.Node) {
|
||||
if n.Kind == yaml.ScalarNode && n.Tag == "!!str" {
|
||||
n.Value = r.String(n.Value)
|
||||
}
|
||||
n.HeadComment = r.String(n.HeadComment)
|
||||
n.LineComment = r.String(n.LineComment)
|
||||
n.FootComment = r.String(n.FootComment)
|
||||
for _, child := range n.Content {
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
visit(&node)
|
||||
return yaml.Marshal(&node)
|
||||
}
|
||||
Reference in New Issue
Block a user