fs: don't log the contents of objects without a String method
The logging functions take an object which is rendered into the log line. Rendering it with %+v dumps all its fields, which for an object holding backend config would include decrypted credentials. Every object currently logged is a string or has a String method, so render anything else as just its type to keep credentials out of the logs. See: https://github.com/rclone/rclone/security/code-scanning/183
This commit is contained in:
@@ -146,8 +146,17 @@ func logSlog(level LogLevel, text string, attrs []any) {
|
||||
|
||||
func logSlogWithObject(level LogLevel, o any, text string, attrs []any) {
|
||||
if o != nil {
|
||||
var object string
|
||||
switch o.(type) {
|
||||
case fmt.Stringer, string:
|
||||
object = fmt.Sprint(o)
|
||||
default:
|
||||
// Don't render the fields of arbitrary objects as they
|
||||
// may contain sensitive data such as credentials.
|
||||
object = fmt.Sprintf("%T", o)
|
||||
}
|
||||
attrs = slices.Concat(attrs, []any{
|
||||
"object", fmt.Sprintf("%+v", o),
|
||||
"object", object,
|
||||
"objectType", fmt.Sprintf("%T", o),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -32,6 +34,30 @@ func TestLogValue(t *testing.T) {
|
||||
assert.Equal(t, "", x.String())
|
||||
}
|
||||
|
||||
func TestLogSlogWithObject(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
oldLogger := logger
|
||||
defer func() { logger = oldLogger }()
|
||||
SetLogger(slog.NewTextHandler(&buf, nil))
|
||||
|
||||
// Objects with a String method are rendered with it
|
||||
logSlogWithObject(LogLevelError, withString{}, "message", nil)
|
||||
assert.Contains(t, buf.String(), "object=hello")
|
||||
|
||||
// Plain strings are rendered as themselves
|
||||
buf.Reset()
|
||||
logSlogWithObject(LogLevelError, "potato", "message", nil)
|
||||
assert.Contains(t, buf.String(), "object=potato")
|
||||
|
||||
// Anything else shows only its type as it may contain
|
||||
// sensitive data such as credentials
|
||||
buf.Reset()
|
||||
type secrets struct{ Password string }
|
||||
logSlogWithObject(LogLevelError, &secrets{Password: "SECRET"}, "message", nil)
|
||||
assert.Contains(t, buf.String(), "object=*fs.secrets")
|
||||
assert.NotContains(t, buf.String(), "SECRET")
|
||||
}
|
||||
|
||||
func TestLogLevelString(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in LogLevel
|
||||
|
||||
Reference in New Issue
Block a user