// 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 } }