thread-master/apps/backend/internal/lib/securelog/securelog.go

65 lines
1.9 KiB
Go

// Package securelog centralizes log hygiene for secrets.
//
// Policy:
// - Never log password / new_password / current_password / temporary_password
// - Never log password_hash, refresh_token, access_token, api keys
// - go-zero mon dumps full Mongo docs at info — call SilenceMongo() at process start
package securelog
import (
"encoding/json"
"regexp"
"strings"
"github.com/zeromicro/go-zero/core/stores/mon"
)
// sensitiveJSONKeys matched case-insensitively as JSON object keys.
var sensitiveKeyPattern = regexp.MustCompile(
`(?i)("(?:password|new_password|current_password|temporary_password|password_hash|` +
`access_token|refresh_token|api_key|secret|authorization|cookie)"\s*:\s*)` +
`(?:"(?:\\.|[^"\\])*"|null|true|false|-?\d+(?:\.\d+)?)`,
)
// SilenceMongo disables go-zero mon command logging (including document dumps).
// monc uses mon under the hood; without this, ReplaceOne logs password_hash.
func SilenceMongo() {
mon.DisableLog()
}
// RedactJSON replaces known secret fields in a JSON blob with "[REDACTED]".
func RedactJSON(raw string) string {
if raw == "" {
return raw
}
return sensitiveKeyPattern.ReplaceAllString(raw, `${1}"[REDACTED]"`)
}
// RedactAny marshals v then redacts; for ad-hoc logx of structs.
func RedactAny(v any) string {
if v == nil {
return "null"
}
b, err := json.Marshal(v)
if err != nil {
return "[unserializable]"
}
return RedactJSON(string(b))
}
// MaskPassword returns a fixed placeholder (never the input).
func MaskPassword(_ string) string { return "[REDACTED]" }
// IsSensitiveKey reports if a field name must never be logged.
func IsSensitiveKey(name string) bool {
n := strings.ToLower(strings.TrimSpace(name))
switch n {
case "password", "new_password", "current_password", "temporary_password",
"password_hash", "access_token", "refresh_token", "api_key", "secret",
"authorization", "cookie", "content": // media content is base64 blob
return true
default:
return false
}
}