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) {
|
func logSlogWithObject(level LogLevel, o any, text string, attrs []any) {
|
||||||
if o != nil {
|
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{
|
attrs = slices.Concat(attrs, []any{
|
||||||
"object", fmt.Sprintf("%+v", o),
|
"object", object,
|
||||||
"objectType", fmt.Sprintf("%T", o),
|
"objectType", fmt.Sprintf("%T", o),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package fs
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -32,6 +34,30 @@ func TestLogValue(t *testing.T) {
|
|||||||
assert.Equal(t, "", x.String())
|
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) {
|
func TestLogLevelString(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
in LogLevel
|
in LogLevel
|
||||||
|
|||||||
Reference in New Issue
Block a user