- Add internal/audit package with JSON() and JSONWithRoles() helpers
that use json.Marshal instead of fmt.Sprintf with %q
- Replace all fmt.Sprintf audit detail construction in:
- internal/server/server.go (10 occurrences)
- internal/ui/handlers_auth.go (4 occurrences)
- internal/grpcserver/auth.go (4 occurrences)
- Add tests for the helpers including edge-case Unicode,
null bytes, special characters, and odd argument counts
- Fix broken {"roles":%v} formatting that produced invalid JSON
Security: Audit log detail strings are now constructed via
json.Marshal, which correctly handles all Unicode edge cases
(U+2028, U+2029, null bytes, etc.) that fmt.Sprintf with %q
may mishandle. This prevents potential log injection or parsing
issues in audit event consumers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
34 lines
898 B
Go
34 lines
898 B
Go
// Package audit provides helpers for constructing audit log detail strings.
|
|
package audit
|
|
|
|
import "encoding/json"
|
|
|
|
// JSON builds a JSON details string from key-value pairs for audit logging.
|
|
// Uses json.Marshal for safe encoding rather than fmt.Sprintf with %q,
|
|
// which is fragile for edge-case Unicode.
|
|
func JSON(pairs ...string) string {
|
|
if len(pairs)%2 != 0 {
|
|
return "{}"
|
|
}
|
|
m := make(map[string]string, len(pairs)/2)
|
|
for i := 0; i < len(pairs); i += 2 {
|
|
m[pairs[i]] = pairs[i+1]
|
|
}
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return "{}"
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// JSONWithRoles builds a JSON details string that includes a "roles" key
|
|
// mapped to a string slice. This produces a proper JSON array for the value.
|
|
func JSONWithRoles(roles []string) string {
|
|
m := map[string][]string{"roles": roles}
|
|
b, err := json.Marshal(m)
|
|
if err != nil {
|
|
return "{}"
|
|
}
|
|
return string(b)
|
|
}
|