From b8ab3679c05d7a7b1df7545bf628b37270a1e03d Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Fri, 5 Dec 2025 18:38:04 +0300 Subject: [PATCH 1/9] Excluded the sensitive credential data in the connection string (DSN, data source name) from error messages for security reasons. --- CHANGELOG.md | 2 ++ driver.go | 3 ++- internal/secret/dsn.go | 30 ++++++++++++++++++++++++++++++ internal/secret/dsn_test.go | 35 +++++++++++++++++++++++++++++++++++ options.go | 3 ++- sql.go | 6 +++++- 6 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 internal/secret/dsn.go create mode 100644 internal/secret/dsn_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index aae34facd..60d4a0bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +* Excluded the sensitive credential data in the connection string (DSN, data source name) from error messages for security reasons + ## v3.121.0 * Changed internal pprof label to pyroscope supported format * Added `query.ImplicitTxControl()` transaction control (the same as `query.NoTx()` and `query.EmptyTxControl()`). See more about implicit transactions on [ydb.tech](https://ydb.tech/docs/en/concepts/transactions?version=v25.2#implicit) diff --git a/driver.go b/driver.go index e838f53aa..6fec52c89 100644 --- a/driver.go +++ b/driver.go @@ -31,6 +31,7 @@ import ( schemeConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/config" internalScripting "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting" scriptingConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/secret" "github.com/ydb-platform/ydb-go-sdk/v3/internal/stack" internalTable "github.com/ydb-platform/ydb-go-sdk/v3/internal/table" tableConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config" @@ -290,7 +291,7 @@ func Open(ctx context.Context, dsn string, opts ...Option) (_ *Driver, _ error) if parser := dsnParsers[parserIdx]; parser != nil { optsFromParser, err := parser(dsn) if err != nil { - return nil, xerrors.WithStackTrace(fmt.Errorf("data source name '%s' wrong: %w", dsn, err)) + return nil, xerrors.WithStackTrace(fmt.Errorf("data source name '%s' wrong: %w", secret.DSN(dsn), err)) } opts = append(opts, optsFromParser...) } diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go new file mode 100644 index 000000000..2e81e3b69 --- /dev/null +++ b/internal/secret/dsn.go @@ -0,0 +1,30 @@ +package secret + +import ( + "net/url" + + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring" +) + +func DSN(dsn string) string { + u, err := url.Parse(dsn) + if err != nil { + return dsn + } + + values := u.Query() + delete(values, "login") + delete(values, "user") + delete(values, "password") + + buffer := xstring.Buffer() + defer buffer.Free() + + buffer.WriteString(u.Scheme + "://" + u.Host + u.Path) + + if len(values) > 0 { + buffer.WriteString("?" + values.Encode()) + } + + return buffer.String() +} diff --git a/internal/secret/dsn_test.go b/internal/secret/dsn_test.go new file mode 100644 index 000000000..a99de3e09 --- /dev/null +++ b/internal/secret/dsn_test.go @@ -0,0 +1,35 @@ +package secret + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDSN(t *testing.T) { + for _, tt := range []struct { + src string + res string + }{ + { + src: "grpc://debuguser:debugpassword@localhost:2136/local1", + res: "grpc://localhost:2136/local1", + }, + { + src: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword", + res: "grpc://localhost:2136/local1", + }, + { + src: "grpc://localhost:2136/local1?login=debuguser&password=debugpassword", + res: "grpc://localhost:2136/local1", + }, + { + src: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", + res: "grpc://localhost:2136/local1?param1=value1¶m2=value2¶m2=value3", + }, + } { + t.Run(tt.src, func(t *testing.T) { + require.Equal(t, tt.res, DSN(tt.src)) + }) + } +} diff --git a/options.go b/options.go index 15c083eae..76c499bf8 100644 --- a/options.go +++ b/options.go @@ -21,6 +21,7 @@ import ( ratelimiterConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/ratelimiter/config" schemeConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scheme/config" scriptingConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/scripting/config" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/secret" tableConfig "github.com/ydb-platform/ydb-go-sdk/v3/internal/table/config" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql" @@ -203,7 +204,7 @@ func WithConnectionString(connectionString string) Option { info, err := dsn.Parse(connectionString) if err != nil { return xerrors.WithStackTrace( - fmt.Errorf("parse connection string '%s' failed: %w", connectionString, err), + fmt.Errorf("parse connection string '%s' failed: %w", secret.DSN(connectionString), err), ) } d.options = append(d.options, info.Options...) diff --git a/sql.go b/sql.go index 2ee1ecd32..e8d9d5ad7 100644 --- a/sql.go +++ b/sql.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ydb-platform/ydb-go-sdk/v3/internal/bind" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/secret" "github.com/ydb-platform/ydb-go-sdk/v3/internal/tx" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors" "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql" @@ -47,7 +48,10 @@ func (d *sqlDriver) Open(string) (driver.Conn, error) { func (d *sqlDriver) OpenConnector(dataSourceName string) (driver.Connector, error) { db, err := Open(context.Background(), dataSourceName) if err != nil { - return nil, xerrors.WithStackTrace(fmt.Errorf("failed to connect by data source name '%s': %w", dataSourceName, err)) + return nil, xerrors.WithStackTrace(fmt.Errorf( + "failed to connect by data source name '%s': %w", + secret.DSN(dataSourceName), err, + )) } c, err := Connector(db, append(db.databaseSQLOptions, From 0c6f07e6917b31ceb5eb18159c92d27a877da8d5 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Fri, 5 Dec 2025 19:21:02 +0300 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/secret/dsn.go | 3 ++- internal/secret/dsn_test.go | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go index 2e81e3b69..725878787 100644 --- a/internal/secret/dsn.go +++ b/internal/secret/dsn.go @@ -9,13 +9,14 @@ import ( func DSN(dsn string) string { u, err := url.Parse(dsn) if err != nil { - return dsn + return "" } values := u.Query() delete(values, "login") delete(values, "user") delete(values, "password") + delete(values, "token") buffer := xstring.Buffer() defer buffer.Free() diff --git a/internal/secret/dsn_test.go b/internal/secret/dsn_test.go index a99de3e09..fc3458b7b 100644 --- a/internal/secret/dsn_test.go +++ b/internal/secret/dsn_test.go @@ -11,6 +11,10 @@ func TestDSN(t *testing.T) { src string res string }{ + { + src: "not-a-valid-url", + res: "", + }, { src: "grpc://debuguser:debugpassword@localhost:2136/local1", res: "grpc://localhost:2136/local1", @@ -27,6 +31,10 @@ func TestDSN(t *testing.T) { src: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", res: "grpc://localhost:2136/local1?param1=value1¶m2=value2¶m2=value3", }, + { + src: "grpc://localhost:2136/local1?token=secrettoken123", + res: "grpc://localhost:2136/local1", + }, } { t.Run(tt.src, func(t *testing.T) { require.Equal(t, tt.res, DSN(tt.src)) From c1128f56a682e72c8b335bf8caea7613d5a22ad5 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Fri, 5 Dec 2025 19:25:55 +0300 Subject: [PATCH 3/9] fixes --- internal/secret/dsn_test.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/secret/dsn_test.go b/internal/secret/dsn_test.go index fc3458b7b..ecaea8ef7 100644 --- a/internal/secret/dsn_test.go +++ b/internal/secret/dsn_test.go @@ -8,36 +8,36 @@ import ( func TestDSN(t *testing.T) { for _, tt := range []struct { - src string - res string + dsn string + exp string }{ { - src: "not-a-valid-url", - res: "", + dsn: "grpc://192.168.0.%31:2136/", + exp: "", }, { - src: "grpc://debuguser:debugpassword@localhost:2136/local1", - res: "grpc://localhost:2136/local1", + dsn: "grpc://debuguser:debugpassword@localhost:2136/local1", + exp: "grpc://localhost:2136/local1", }, { - src: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword", - res: "grpc://localhost:2136/local1", + dsn: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword", + exp: "grpc://localhost:2136/local1", }, { - src: "grpc://localhost:2136/local1?login=debuguser&password=debugpassword", - res: "grpc://localhost:2136/local1", + dsn: "grpc://localhost:2136/local1?login=debuguser&password=debugpassword", + exp: "grpc://localhost:2136/local1", }, { - src: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", - res: "grpc://localhost:2136/local1?param1=value1¶m2=value2¶m2=value3", + dsn: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", + exp: "grpc://localhost:2136/local1?param1=value1¶m2=value2¶m2=value3", }, { - src: "grpc://localhost:2136/local1?token=secrettoken123", - res: "grpc://localhost:2136/local1", + dsn: "grpc://localhost:2136/local1?token=secrettoken123", + exp: "grpc://localhost:2136/local1", }, } { - t.Run(tt.src, func(t *testing.T) { - require.Equal(t, tt.res, DSN(tt.src)) + t.Run(tt.dsn, func(t *testing.T) { + require.Equal(t, tt.exp, DSN(tt.dsn)) }) } } From 2e442674cfae2329d7d36141c210e9c12c6d43df Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 11:37:46 +0300 Subject: [PATCH 4/9] fixes --- internal/secret/dsn.go | 43 +++++++++++++++++++++++++------- internal/secret/dsn_test.go | 14 +++++++---- internal/secret/mask.go | 20 +++++++++++++++ internal/secret/mask_test.go | 35 ++++++++++++++++++++++++++ internal/secret/password.go | 21 +--------------- internal/secret/password_test.go | 10 +++++++- 6 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 internal/secret/mask.go create mode 100644 internal/secret/mask_test.go diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go index 725878787..c480133fd 100644 --- a/internal/secret/dsn.go +++ b/internal/secret/dsn.go @@ -2,6 +2,7 @@ package secret import ( "net/url" + "strings" "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring" ) @@ -12,19 +13,43 @@ func DSN(dsn string) string { return "" } - values := u.Query() - delete(values, "login") - delete(values, "user") - delete(values, "password") - delete(values, "token") - buffer := xstring.Buffer() defer buffer.Free() - buffer.WriteString(u.Scheme + "://" + u.Host + u.Path) + buffer.WriteString(u.Scheme + "://") + + if u.User != nil { + buffer.WriteString(u.User.Username()) + if password, has := u.User.Password(); has { + buffer.WriteString(":" + Password(password)) + } + buffer.WriteString("@") + } - if len(values) > 0 { - buffer.WriteString("?" + values.Encode()) + buffer.WriteString(u.Host) + buffer.WriteString(u.Path) + + if len(u.RawQuery) > 0 { + buffer.WriteString("?") + + params := strings.Split(u.RawQuery, "&") + + for i, param := range params { + if i > 0 { + buffer.WriteString("&") + } + paramValue := strings.Split(param, "=") + buffer.WriteString(paramValue[0]) + if len(paramValue) > 1 { + buffer.WriteString("=") + switch paramValue[0] { + case "token", "password": + buffer.WriteString(Mask(paramValue[1])) + default: + buffer.WriteString(paramValue[1]) + } + } + } } return buffer.String() diff --git a/internal/secret/dsn_test.go b/internal/secret/dsn_test.go index ecaea8ef7..e91ef21ee 100644 --- a/internal/secret/dsn_test.go +++ b/internal/secret/dsn_test.go @@ -17,23 +17,27 @@ func TestDSN(t *testing.T) { }, { dsn: "grpc://debuguser:debugpassword@localhost:2136/local1", - exp: "grpc://localhost:2136/local1", + exp: "grpc://debuguser:d***********d@localhost:2136/local1", }, { dsn: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword", - exp: "grpc://localhost:2136/local1", + exp: "grpc://localhost:2136/local1?user=debuguser&password=d***********d", }, { dsn: "grpc://localhost:2136/local1?login=debuguser&password=debugpassword", - exp: "grpc://localhost:2136/local1", + exp: "grpc://localhost:2136/local1?login=debuguser&password=d***********d", }, { dsn: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", - exp: "grpc://localhost:2136/local1?param1=value1¶m2=value2¶m2=value3", + exp: "grpc://localhost:2136/local1?param1=value1&login=debuguser¶m2=value2&password=d***********d¶m2=value3", + }, + { + dsn: "grpc://localhost:2136/local1?param1&login=debuguser¶m2=value2&password=debugpassword¶m2=value3", + exp: "grpc://localhost:2136/local1?param1&login=debuguser¶m2=value2&password=d***********d¶m2=value3", }, { dsn: "grpc://localhost:2136/local1?token=secrettoken123", - exp: "grpc://localhost:2136/local1", + exp: "grpc://localhost:2136/local1?token=s************3", }, } { t.Run(tt.dsn, func(t *testing.T) { diff --git a/internal/secret/mask.go b/internal/secret/mask.go new file mode 100644 index 000000000..578504326 --- /dev/null +++ b/internal/secret/mask.go @@ -0,0 +1,20 @@ +package secret + +func Mask(s string) string { + var ( + runes = []rune(s) + startPosition = 1 + endPosition = len(runes) - 1 + ) + + if len(runes) < 5 { + startPosition = 0 + endPosition = len(runes) + } + + for i := startPosition; i < endPosition; i++ { + runes[i] = '*' + } + + return string(runes) +} diff --git a/internal/secret/mask_test.go b/internal/secret/mask_test.go new file mode 100644 index 000000000..7078b4df9 --- /dev/null +++ b/internal/secret/mask_test.go @@ -0,0 +1,35 @@ +package secret + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMask(t *testing.T) { + for _, tt := range []struct { + s string + exp string + }{ + { + s: "test", + exp: "****", + }, + { + s: "test-long-password", + exp: "t****************d", + }, + { + s: "пароль", + exp: "п****ь", + }, + { + s: "пар", + exp: "***", + }, + } { + t.Run("", func(t *testing.T) { + require.Equal(t, tt.exp, Mask(tt.s)) + }) + } +} diff --git a/internal/secret/password.go b/internal/secret/password.go index 888d07525..8351f5a15 100644 --- a/internal/secret/password.go +++ b/internal/secret/password.go @@ -1,24 +1,5 @@ package secret -import ( - "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xstring" -) - func Password(password string) string { - var ( - bytes = []byte(password) - startPosition = 3 - endPosition = len(bytes) - 2 - ) - if startPosition > endPosition { - for i := range bytes { - bytes[i] = '*' - } - } else { - for i := startPosition; i < endPosition; i++ { - bytes[i] = '*' - } - } - - return xstring.FromBytes(bytes) + return Mask(password) } diff --git a/internal/secret/password_test.go b/internal/secret/password_test.go index 5570ae6ef..8bd2c4aa2 100644 --- a/internal/secret/password_test.go +++ b/internal/secret/password_test.go @@ -17,7 +17,15 @@ func TestPassword(t *testing.T) { }, { password: "test-long-password", - exp: "tes*************rd", + exp: "t****************d", + }, + { + password: "пароль", + exp: "п****ь", + }, + { + password: "пар", + exp: "***", }, } { t.Run("", func(t *testing.T) { From 75e40e26fa5155ff628100ad81d9a429b08e14d1 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 11:42:42 +0300 Subject: [PATCH 5/9] fix --- driver_string_test.go | 2 +- internal/credentials/errors_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/driver_string_test.go b/driver_string_test.go index eda19532b..072267907 100644 --- a/driver_string_test.go +++ b/driver_string_test.go @@ -52,7 +52,7 @@ func TestDriver_String(t *testing.T) { config.WithSecure(true), config.WithCredentials(credentials.NewStaticCredentials("user", "password", "")), )}, - s: `Driver{Endpoint:"localhost",Database:"local",Secure:true,Credentials:Static{User:"user",Password:"pas***rd",Token:"****(CRC-32c: 00000000)",From:"github.com/ydb-platform/ydb-go-sdk/v3/credentials.NewStaticCredentials(credentials.go:35)"}}`, //nolint:lll + s: `Driver{Endpoint:"localhost",Database:"local",Secure:true,Credentials:Static{User:"user",Password:"p******d",Token:"****(CRC-32c: 00000000)",From:"github.com/ydb-platform/ydb-go-sdk/v3/credentials.NewStaticCredentials(credentials.go:35)"}}`, //nolint:lll }, { name: xtest.CurrentFileLine(), diff --git a/internal/credentials/errors_test.go b/internal/credentials/errors_test.go index 0971ab177..cfe48f2b1 100644 --- a/internal/credentials/errors_test.go +++ b/internal/credentials/errors_test.go @@ -104,7 +104,7 @@ func TestAccessError(t *testing.T) { errorString: "something went wrong (" + "endpoint:\"grps://localhost:2135\"," + "database:\"/local\"," + - "credentials:\"Static{User:\\\"USER\\\",Password:\\\"SEC**********RD\\\",Token:\\\"****(CRC-32c: 00000000)\\\"}\"" + //nolint:lll + "credentials:\"Static{User:\\\"USER\\\",Password:\\\"S*************D\\\",Token:\\\"****(CRC-32c: 00000000)\\\"}\"" + //nolint:lll "): test " + "at `github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials.TestAccessError(errors_test.go:93)`", }, @@ -123,7 +123,7 @@ func TestAccessError(t *testing.T) { errorString: "something went wrong (" + "endpoint:\"grps://localhost:2135\"," + "database:\"/local\"," + - "credentials:\"Static{User:\\\"USER\\\",Password:\\\"SEC**********RD\\\",Token:\\\"****(CRC-32c: 00000000)\\\",From:\\\"TestAccessError\\\"}\"" + //nolint:lll + "credentials:\"Static{User:\\\"USER\\\",Password:\\\"S*************D\\\",Token:\\\"****(CRC-32c: 00000000)\\\",From:\\\"TestAccessError\\\"}\"" + //nolint:lll "): test " + "at `github.com/ydb-platform/ydb-go-sdk/v3/internal/credentials.TestAccessError(errors_test.go:112)`", }, From 837ef17fccdecc4dd1ba540d4b8ba9e0e13504d3 Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 11:44:44 +0300 Subject: [PATCH 6/9] CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d4a0bf3..a2b75c64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -* Excluded the sensitive credential data in the connection string (DSN, data source name) from error messages for security reasons +* Masked the sensitive credential data in the connection string (DSN, data source name) from error messages for security reasons ## v3.121.0 * Changed internal pprof label to pyroscope supported format From aae1637f46115d1a6e1f19676163f9a24abb553e Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 12:32:45 +0300 Subject: [PATCH 7/9] fixes --- internal/secret/dsn.go | 8 +++++++- internal/secret/dsn_test.go | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go index c480133fd..2e7535c50 100644 --- a/internal/secret/dsn.go +++ b/internal/secret/dsn.go @@ -16,7 +16,9 @@ func DSN(dsn string) string { buffer := xstring.Buffer() defer buffer.Free() - buffer.WriteString(u.Scheme + "://") + if u.Scheme != "" { + buffer.WriteString(u.Scheme + "://") + } if u.User != nil { buffer.WriteString(u.User.Username()) @@ -52,5 +54,9 @@ func DSN(dsn string) string { } } + if u.Fragment != "" { + buffer.WriteString("#" + u.Fragment) + } + return buffer.String() } diff --git a/internal/secret/dsn_test.go b/internal/secret/dsn_test.go index e91ef21ee..89f1ee208 100644 --- a/internal/secret/dsn_test.go +++ b/internal/secret/dsn_test.go @@ -19,6 +19,14 @@ func TestDSN(t *testing.T) { dsn: "grpc://debuguser:debugpassword@localhost:2136/local1", exp: "grpc://debuguser:d***********d@localhost:2136/local1", }, + { + dsn: "grpc://host/db?password=pass%3Dword", + exp: "grpc://host/db?password=p*********d", + }, + { + dsn: "grpc://host/db?password=pass%26word", + exp: "grpc://host/db?password=p*********d", + }, { dsn: "grpc://localhost:2136/local1?user=debuguser&password=debugpassword", exp: "grpc://localhost:2136/local1?user=debuguser&password=d***********d", From f3a9cfbf02d601d6f3fcc4b4cf6180fd3a78f8cb Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 12:34:02 +0300 Subject: [PATCH 8/9] fix --- internal/secret/dsn.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go index 2e7535c50..033b71060 100644 --- a/internal/secret/dsn.go +++ b/internal/secret/dsn.go @@ -34,13 +34,13 @@ func DSN(dsn string) string { if len(u.RawQuery) > 0 { buffer.WriteString("?") - params := strings.Split(u.RawQuery, "&") + params := strings.SplitN(u.RawQuery, "&", 2) for i, param := range params { if i > 0 { buffer.WriteString("&") } - paramValue := strings.Split(param, "=") + paramValue := strings.SplitN(param, "=", 2) buffer.WriteString(paramValue[0]) if len(paramValue) > 1 { buffer.WriteString("=") From 962b353601350ac45da14c802ac9e430b474b18e Mon Sep 17 00:00:00 2001 From: Aleksey Myasnikov Date: Sat, 6 Dec 2025 12:35:05 +0300 Subject: [PATCH 9/9] fix --- internal/secret/dsn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/secret/dsn.go b/internal/secret/dsn.go index 033b71060..be205c770 100644 --- a/internal/secret/dsn.go +++ b/internal/secret/dsn.go @@ -34,7 +34,7 @@ func DSN(dsn string) string { if len(u.RawQuery) > 0 { buffer.WriteString("?") - params := strings.SplitN(u.RawQuery, "&", 2) + params := strings.Split(u.RawQuery, "&") for i, param := range params { if i > 0 {