From 781a316d998784ba5dfb4187a581ba8116954fc5 Mon Sep 17 00:00:00 2001 From: dongmen <414110582@qq.com> Date: Tue, 19 May 2026 11:06:06 +0800 Subject: [PATCH 1/4] api,sink: mask sink uri secrets in errors (cherry picked from commit 4d95fd45986551e169dfdfa4984151b1942be85c) --- api/v2/changefeed.go | 28 +++++++---- api/v2/changefeed_test.go | 39 +++++++++++++++ downstreamadapter/sink/sink.go | 13 +++-- downstreamadapter/sink/sink_test.go | 75 +++++++++++++++++++++++++++++ pkg/check/cluster.go | 3 +- pkg/util/uri.go | 14 ++++-- pkg/util/uri_test.go | 29 +++++++++++ 7 files changed, 181 insertions(+), 20 deletions(-) create mode 100644 api/v2/changefeed_test.go create mode 100644 downstreamadapter/sink/sink_test.go create mode 100644 pkg/util/uri_test.go diff --git a/api/v2/changefeed.go b/api/v2/changefeed.go index 2524389312..dc7607e326 100644 --- a/api/v2/changefeed.go +++ b/api/v2/changefeed.go @@ -52,6 +52,14 @@ import ( "go.uber.org/zap" ) +func maskSinkURIForError(sinkURI string) string { + return util.MaskSensitiveDataInURIForError(sinkURI) +} + +func genSinkURIInvalidError(sinkURI string) error { + return errors.ErrSinkURIInvalid.GenWithStackByArgs(maskSinkURIForError(sinkURI)) +} + // CreateChangefeed handles create changefeed request, // it returns the changefeed's changefeedInfo that it just created // CreateChangefeed creates a changefeed @@ -135,7 +143,7 @@ func (h *OpenAPIV2) CreateChangefeed(c *gin.Context) { } sinkURIParsed, err := url.Parse(cfg.SinkURI) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfg.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfg.SinkURI)) return } @@ -144,7 +152,7 @@ func (h *OpenAPIV2) CreateChangefeed(c *gin.Context) { if config.IsMQScheme(scheme) { topic, err = helper.GetTopic(sinkURIParsed) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfg.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(cfg.SinkURI))) return } } @@ -291,7 +299,7 @@ func (h *OpenAPIV2) CreateChangefeed(c *gin.Context) { } err = sink.Verify(ctx, cfConfig, changefeedID) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfg.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(cfg.SinkURI))) return } @@ -431,7 +439,7 @@ func (h *OpenAPIV2) VerifyTable(c *gin.Context) { // verify replicaConfig sinkURIParsed, err := url.Parse(cfg.SinkURI) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfg.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfg.SinkURI)) return } err = replicaCfg.ValidateAndAdjust(sinkURIParsed) @@ -445,7 +453,7 @@ func (h *OpenAPIV2) VerifyTable(c *gin.Context) { if config.IsMQScheme(scheme) { topic, err = helper.GetTopic(sinkURIParsed) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfg.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(cfg.SinkURI))) return } } @@ -822,14 +830,14 @@ func (h *OpenAPIV2) ResumeChangefeed(c *gin.Context) { ) sinkURIParsed, err = url.Parse(cfInfo.SinkURI) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfInfo.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfInfo.SinkURI)) return } scheme := sinkURIParsed.Scheme if config.IsMQScheme(scheme) { topic, err = helper.GetTopic(sinkURIParsed) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, cfInfo.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(cfInfo.SinkURI))) return } } @@ -975,7 +983,7 @@ func (h *OpenAPIV2) UpdateChangefeed(c *gin.Context) { // verify replicaConfig sinkURIParsed, err := url.Parse(oldCfInfo.SinkURI) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, oldCfInfo.SinkURI)) + _ = c.Error(genSinkURIInvalidError(oldCfInfo.SinkURI)) return } err = oldCfInfo.Config.ValidateAndAdjust(sinkURIParsed) @@ -989,7 +997,7 @@ func (h *OpenAPIV2) UpdateChangefeed(c *gin.Context) { if config.IsMQScheme(scheme) { topic, err = helper.GetTopic(sinkURIParsed) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, oldCfInfo.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(oldCfInfo.SinkURI))) return } } @@ -1033,7 +1041,7 @@ func (h *OpenAPIV2) UpdateChangefeed(c *gin.Context) { err = sink.Verify(ctx, oldCfInfo.ToChangefeedConfig(), oldCfInfo.ChangefeedID) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, oldCfInfo.SinkURI)) + _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(oldCfInfo.SinkURI))) return } diff --git a/api/v2/changefeed_test.go b/api/v2/changefeed_test.go new file mode 100644 index 0000000000..126aa1d583 --- /dev/null +++ b/api/v2/changefeed_test.go @@ -0,0 +1,39 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMaskSinkURIForError(t *testing.T) { + sinkURI := "kafka://127.0.0.1:9092/topic?protocol=canal-json" + + "&sasl-user=ticdc&sasl-password=verysecure&secret-access-key=rawsecret" + + maskedURI := maskSinkURIForError(sinkURI) + require.NotContains(t, maskedURI, "verysecure") + require.NotContains(t, maskedURI, "rawsecret") + require.Contains(t, maskedURI, "sasl-password=xxxxx") + require.Contains(t, maskedURI, "secret-access-key=xxxxx") + require.Contains(t, maskedURI, "sasl-user=ticdc") + + invalidURI := "mysql://root:verysecure@127.0.0.1/%zz" + require.Equal(t, "", maskSinkURIForError(invalidURI)) + + err := genSinkURIInvalidError(invalidURI) + require.NotContains(t, err.Error(), "verysecure") + require.Contains(t, err.Error(), "") +} diff --git a/downstreamadapter/sink/sink.go b/downstreamadapter/sink/sink.go index 12a934d2c6..9afcbff34b 100644 --- a/downstreamadapter/sink/sink.go +++ b/downstreamadapter/sink/sink.go @@ -26,6 +26,7 @@ import ( commonEvent "github.com/pingcap/ticdc/pkg/common/event" "github.com/pingcap/ticdc/pkg/config" "github.com/pingcap/ticdc/pkg/errors" + "github.com/pingcap/ticdc/pkg/util" ) type Sink interface { @@ -50,7 +51,8 @@ type Sink interface { func New(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common.ChangeFeedID) (Sink, error) { sinkURI, err := url.Parse(cfg.SinkURI) if err != nil { - return nil, errors.WrapError(errors.ErrSinkURIInvalid, err) + return nil, errors.ErrSinkURIInvalid.GenWithStackByArgs( + util.MaskSensitiveDataInURIForError(cfg.SinkURI)) } scheme := config.GetScheme(sinkURI) switch scheme { @@ -65,13 +67,15 @@ func New(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common. case config.BlackHoleScheme: return blackhole.New(changefeedID) } - return nil, errors.ErrSinkURIInvalid.GenWithStackByArgs(sinkURI) + return nil, errors.ErrSinkURIInvalid.GenWithStackByArgs( + util.MaskSensitiveDataInURIForError(sinkURI.String())) } func Verify(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common.ChangeFeedID) error { sinkURI, err := url.Parse(cfg.SinkURI) if err != nil { - return errors.WrapError(errors.ErrSinkURIInvalid, err) + return errors.ErrSinkURIInvalid.GenWithStackByArgs( + util.MaskSensitiveDataInURIForError(cfg.SinkURI)) } scheme := config.GetScheme(sinkURI) switch scheme { @@ -86,5 +90,6 @@ func Verify(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID comm case config.BlackHoleScheme: return nil } - return errors.ErrSinkURIInvalid.GenWithStackByArgs(sinkURI) + return errors.ErrSinkURIInvalid.GenWithStackByArgs( + util.MaskSensitiveDataInURIForError(sinkURI.String())) } diff --git a/downstreamadapter/sink/sink_test.go b/downstreamadapter/sink/sink_test.go new file mode 100644 index 0000000000..c908fef5e8 --- /dev/null +++ b/downstreamadapter/sink/sink_test.go @@ -0,0 +1,75 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package sink + +import ( + "context" + "testing" + + "github.com/pingcap/ticdc/pkg/common" + "github.com/pingcap/ticdc/pkg/config" + "github.com/stretchr/testify/require" +) + +func TestUnknownSchemeMasksSensitiveSinkURI(t *testing.T) { + t.Parallel() + + changefeedID := common.NewChangefeedID(common.DefaultKeyspaceName) + cfg := &config.ChangefeedConfig{ + SinkURI: "unknown://127.0.0.1:9092/topic?sasl-password=verysecure&access-key=rawkey", + } + + _, err := New(context.Background(), cfg, changefeedID) + require.Error(t, err) + requireMaskedSinkURIError(t, err) + + err = Verify(context.Background(), cfg, changefeedID) + require.Error(t, err) + requireMaskedSinkURIError(t, err) +} + +func TestParseErrorMasksSensitiveSinkURI(t *testing.T) { + t.Parallel() + + changefeedID := common.NewChangefeedID(common.DefaultKeyspaceName) + cfg := &config.ChangefeedConfig{ + SinkURI: "mysql://root:verysecure@127.0.0.1/%zz", + } + + _, err := New(context.Background(), cfg, changefeedID) + require.Error(t, err) + requireInvalidSinkURIError(t, err) + + err = Verify(context.Background(), cfg, changefeedID) + require.Error(t, err) + requireInvalidSinkURIError(t, err) +} + +func requireMaskedSinkURIError(t *testing.T, err error) { + t.Helper() + + errMsg := err.Error() + require.NotContains(t, errMsg, "verysecure") + require.NotContains(t, errMsg, "rawkey") + require.Contains(t, errMsg, "sasl-password=xxxxx") + require.Contains(t, errMsg, "access-key=xxxxx") +} + +func requireInvalidSinkURIError(t *testing.T, err error) { + t.Helper() + + errMsg := err.Error() + require.NotContains(t, errMsg, "verysecure") + require.Contains(t, errMsg, "") +} diff --git a/pkg/check/cluster.go b/pkg/check/cluster.go index d320297a94..89f0581c77 100644 --- a/pkg/check/cluster.go +++ b/pkg/check/cluster.go @@ -103,7 +103,8 @@ func getClusterIDBySinkURI( ) (uint64, string, bool, error) { uri, err := url.Parse(sinkURI) if err != nil { - return 0, "", false, cerrors.WrapError(cerrors.ErrSinkURIInvalid, err, sinkURI) + return 0, "", false, cerrors.ErrSinkURIInvalid.GenWithStackByArgs( + util.MaskSensitiveDataInURIForError(sinkURI)) } scheme := config.GetScheme(uri) diff --git a/pkg/util/uri.go b/pkg/util/uri.go index 18bdb01bd5..868bf73c19 100644 --- a/pkg/util/uri.go +++ b/pkg/util/uri.go @@ -17,9 +17,6 @@ import ( "net" "net/url" "strings" - - "github.com/pingcap/log" - "go.uber.org/zap" ) // IsValidIPv6AddressFormatInURI reports whether hostPort is a valid IPv6 address in URI. @@ -70,7 +67,6 @@ func validOptionalPort(port string) bool { func MaskSinkURI(uri string) (string, error) { uriParsed, err := url.Parse(uri) if err != nil { - log.Error("failed to parse sink URI", zap.Error(err)) return "", err } queries := uriParsed.Query() @@ -99,7 +95,6 @@ var sensitiveQueryParameterNames = []string{ func MaskSensitiveDataInURI(uri string) string { uriParsed, err := url.Parse(uri) if err != nil { - log.Error("failed to parse sink URI", zap.Error(err)) return "" } queries := uriParsed.Query() @@ -114,3 +109,12 @@ func MaskSensitiveDataInURI(uri string) string { uriParsed.RawQuery = queries.Encode() return uriParsed.Redacted() } + +// MaskSensitiveDataInURIForError masks sensitive data in a URI for error messages. +func MaskSensitiveDataInURIForError(uri string) string { + maskedURI := MaskSensitiveDataInURI(uri) + if maskedURI == "" && uri != "" { + return "" + } + return maskedURI +} diff --git a/pkg/util/uri_test.go b/pkg/util/uri_test.go new file mode 100644 index 0000000000..3d692dacfd --- /dev/null +++ b/pkg/util/uri_test.go @@ -0,0 +1,29 @@ +// Copyright 2026 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMaskSensitiveDataInURIForError(t *testing.T) { + require.Equal(t, "", MaskSensitiveDataInURIForError("")) + require.Equal(t, "abc", MaskSensitiveDataInURIForError("abc")) + require.Equal(t, + "mysql://root:xxxxx@127.0.0.1:3306/?sasl-password=xxxxx", + MaskSensitiveDataInURIForError("mysql://root:verysecure@127.0.0.1:3306/?sasl-password=rawsecret")) + require.Equal(t, "", MaskSensitiveDataInURIForError("mysql://root:verysecure@127.0.0.1/%zz")) +} From b29f457eaef90d2cfd260885ea2d31babc024d40 Mon Sep 17 00:00:00 2001 From: dongmen <414110582@qq.com> Date: Tue, 19 May 2026 11:35:00 +0800 Subject: [PATCH 2/4] update git ignore Signed-off-by: dongmen <414110582@qq.com> (cherry picked from commit b6a114a94fc976a540c7c1a8ae171f7b5c17749b) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5012978619..e224245024 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ tools/bin tools/include tools/workload/bin +.design .issue .vscode .idea From cc51c119e768d2c31ceb0531f7ca5e2e162402a9 Mon Sep 17 00:00:00 2001 From: dongmen <414110582@qq.com> Date: Tue, 19 May 2026 11:46:20 +0800 Subject: [PATCH 3/4] api,sink: preserve sanitized uri parse errors (cherry picked from commit 71275a7fdd6b7ba9b3697e96b79ef67d4c8c47d2) --- api/v2/changefeed.go | 13 +++++++------ api/v2/changefeed_test.go | 13 ++++++++++++- downstreamadapter/sink/sink.go | 8 ++++++-- downstreamadapter/sink/sink_test.go | 2 ++ pkg/check/cluster.go | 4 +++- pkg/util/uri.go | 16 ++++++++++++++++ pkg/util/uri_test.go | 12 ++++++++++++ 7 files changed, 58 insertions(+), 10 deletions(-) diff --git a/api/v2/changefeed.go b/api/v2/changefeed.go index dc7607e326..54b716d1fc 100644 --- a/api/v2/changefeed.go +++ b/api/v2/changefeed.go @@ -56,8 +56,9 @@ func maskSinkURIForError(sinkURI string) string { return util.MaskSensitiveDataInURIForError(sinkURI) } -func genSinkURIInvalidError(sinkURI string) error { - return errors.ErrSinkURIInvalid.GenWithStackByArgs(maskSinkURIForError(sinkURI)) +func genSinkURIInvalidError(sinkURI string, err error) error { + return errors.WrapError( + errors.ErrSinkURIInvalid, util.MaskSensitiveDataInURLError(err), maskSinkURIForError(sinkURI)) } // CreateChangefeed handles create changefeed request, @@ -143,7 +144,7 @@ func (h *OpenAPIV2) CreateChangefeed(c *gin.Context) { } sinkURIParsed, err := url.Parse(cfg.SinkURI) if err != nil { - _ = c.Error(genSinkURIInvalidError(cfg.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfg.SinkURI, err)) return } @@ -439,7 +440,7 @@ func (h *OpenAPIV2) VerifyTable(c *gin.Context) { // verify replicaConfig sinkURIParsed, err := url.Parse(cfg.SinkURI) if err != nil { - _ = c.Error(genSinkURIInvalidError(cfg.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfg.SinkURI, err)) return } err = replicaCfg.ValidateAndAdjust(sinkURIParsed) @@ -830,7 +831,7 @@ func (h *OpenAPIV2) ResumeChangefeed(c *gin.Context) { ) sinkURIParsed, err = url.Parse(cfInfo.SinkURI) if err != nil { - _ = c.Error(genSinkURIInvalidError(cfInfo.SinkURI)) + _ = c.Error(genSinkURIInvalidError(cfInfo.SinkURI, err)) return } scheme := sinkURIParsed.Scheme @@ -983,7 +984,7 @@ func (h *OpenAPIV2) UpdateChangefeed(c *gin.Context) { // verify replicaConfig sinkURIParsed, err := url.Parse(oldCfInfo.SinkURI) if err != nil { - _ = c.Error(genSinkURIInvalidError(oldCfInfo.SinkURI)) + _ = c.Error(genSinkURIInvalidError(oldCfInfo.SinkURI, err)) return } err = oldCfInfo.Config.ValidateAndAdjust(sinkURIParsed) diff --git a/api/v2/changefeed_test.go b/api/v2/changefeed_test.go index 126aa1d583..a72f40f64c 100644 --- a/api/v2/changefeed_test.go +++ b/api/v2/changefeed_test.go @@ -14,6 +14,7 @@ package v2 import ( + "net/url" "testing" "github.com/stretchr/testify/require" @@ -33,7 +34,17 @@ func TestMaskSinkURIForError(t *testing.T) { invalidURI := "mysql://root:verysecure@127.0.0.1/%zz" require.Equal(t, "", maskSinkURIForError(invalidURI)) - err := genSinkURIInvalidError(invalidURI) + err := genSinkURIInvalidError(invalidURI, mustParseURLError(t, invalidURI)) require.NotContains(t, err.Error(), "verysecure") require.Contains(t, err.Error(), "") + require.Contains(t, err.Error(), `parse ""`) + require.Contains(t, err.Error(), "invalid URL escape") +} + +func mustParseURLError(t *testing.T, rawURL string) error { + t.Helper() + + _, err := url.Parse(rawURL) + require.Error(t, err) + return err } diff --git a/downstreamadapter/sink/sink.go b/downstreamadapter/sink/sink.go index 9afcbff34b..6e797fc448 100644 --- a/downstreamadapter/sink/sink.go +++ b/downstreamadapter/sink/sink.go @@ -51,7 +51,9 @@ type Sink interface { func New(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common.ChangeFeedID) (Sink, error) { sinkURI, err := url.Parse(cfg.SinkURI) if err != nil { - return nil, errors.ErrSinkURIInvalid.GenWithStackByArgs( + return nil, errors.WrapError( + errors.ErrSinkURIInvalid, + util.MaskSensitiveDataInURLError(err), util.MaskSensitiveDataInURIForError(cfg.SinkURI)) } scheme := config.GetScheme(sinkURI) @@ -74,7 +76,9 @@ func New(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common. func Verify(ctx context.Context, cfg *config.ChangefeedConfig, changefeedID common.ChangeFeedID) error { sinkURI, err := url.Parse(cfg.SinkURI) if err != nil { - return errors.ErrSinkURIInvalid.GenWithStackByArgs( + return errors.WrapError( + errors.ErrSinkURIInvalid, + util.MaskSensitiveDataInURLError(err), util.MaskSensitiveDataInURIForError(cfg.SinkURI)) } scheme := config.GetScheme(sinkURI) diff --git a/downstreamadapter/sink/sink_test.go b/downstreamadapter/sink/sink_test.go index c908fef5e8..0d812aa533 100644 --- a/downstreamadapter/sink/sink_test.go +++ b/downstreamadapter/sink/sink_test.go @@ -72,4 +72,6 @@ func requireInvalidSinkURIError(t *testing.T, err error) { errMsg := err.Error() require.NotContains(t, errMsg, "verysecure") require.Contains(t, errMsg, "") + require.Contains(t, errMsg, `parse ""`) + require.Contains(t, errMsg, "invalid URL escape") } diff --git a/pkg/check/cluster.go b/pkg/check/cluster.go index 89f0581c77..534fdaab41 100644 --- a/pkg/check/cluster.go +++ b/pkg/check/cluster.go @@ -103,7 +103,9 @@ func getClusterIDBySinkURI( ) (uint64, string, bool, error) { uri, err := url.Parse(sinkURI) if err != nil { - return 0, "", false, cerrors.ErrSinkURIInvalid.GenWithStackByArgs( + return 0, "", false, cerrors.WrapError( + cerrors.ErrSinkURIInvalid, + util.MaskSensitiveDataInURLError(err), util.MaskSensitiveDataInURIForError(sinkURI)) } diff --git a/pkg/util/uri.go b/pkg/util/uri.go index 868bf73c19..c67badd5f5 100644 --- a/pkg/util/uri.go +++ b/pkg/util/uri.go @@ -118,3 +118,19 @@ func MaskSensitiveDataInURIForError(uri string) string { } return maskedURI } + +// MaskSensitiveDataInURLError masks the URL carried by net/url errors. +func MaskSensitiveDataInURLError(err error) error { + if err == nil { + return nil + } + urlErr, ok := err.(*url.Error) + if !ok { + return err + } + return &url.Error{ + Op: urlErr.Op, + URL: MaskSensitiveDataInURIForError(urlErr.URL), + Err: urlErr.Err, + } +} diff --git a/pkg/util/uri_test.go b/pkg/util/uri_test.go index 3d692dacfd..1cd747428f 100644 --- a/pkg/util/uri_test.go +++ b/pkg/util/uri_test.go @@ -14,6 +14,7 @@ package util import ( + "net/url" "testing" "github.com/stretchr/testify/require" @@ -27,3 +28,14 @@ func TestMaskSensitiveDataInURIForError(t *testing.T) { MaskSensitiveDataInURIForError("mysql://root:verysecure@127.0.0.1:3306/?sasl-password=rawsecret")) require.Equal(t, "", MaskSensitiveDataInURIForError("mysql://root:verysecure@127.0.0.1/%zz")) } + +func TestMaskSensitiveDataInURLError(t *testing.T) { + rawURL := "mysql://root:verysecure@127.0.0.1/%zz" + _, err := url.Parse(rawURL) + require.Error(t, err) + + maskedErr := MaskSensitiveDataInURLError(err) + require.NotContains(t, maskedErr.Error(), "verysecure") + require.Contains(t, maskedErr.Error(), `parse ""`) + require.Contains(t, maskedErr.Error(), "invalid URL escape") +} From 6b59ccab62c71e662a381f2af79bab66d1e0fdb4 Mon Sep 17 00:00:00 2001 From: dongmen <414110582@qq.com> Date: Fri, 12 Jun 2026 14:24:06 +0800 Subject: [PATCH 4/4] api,sink: mask wrapped sink uri errors --- api/v2/changefeed.go | 4 ++-- api/v2/changefeed_test.go | 7 +++++++ pkg/util/uri.go | 12 +++++------- pkg/util/uri_test.go | 10 ++++++++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/api/v2/changefeed.go b/api/v2/changefeed.go index 54b716d1fc..e0eb54519e 100644 --- a/api/v2/changefeed.go +++ b/api/v2/changefeed.go @@ -300,7 +300,7 @@ func (h *OpenAPIV2) CreateChangefeed(c *gin.Context) { } err = sink.Verify(ctx, cfConfig, changefeedID) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(cfg.SinkURI))) + _ = c.Error(genSinkURIInvalidError(cfg.SinkURI, err)) return } @@ -1042,7 +1042,7 @@ func (h *OpenAPIV2) UpdateChangefeed(c *gin.Context) { err = sink.Verify(ctx, oldCfInfo.ToChangefeedConfig(), oldCfInfo.ChangefeedID) if err != nil { - _ = c.Error(errors.WrapError(errors.ErrSinkURIInvalid, err, maskSinkURIForError(oldCfInfo.SinkURI))) + _ = c.Error(genSinkURIInvalidError(oldCfInfo.SinkURI, err)) return } diff --git a/api/v2/changefeed_test.go b/api/v2/changefeed_test.go index a72f40f64c..76a161d6e3 100644 --- a/api/v2/changefeed_test.go +++ b/api/v2/changefeed_test.go @@ -17,6 +17,7 @@ import ( "net/url" "testing" + perrors "github.com/pingcap/errors" "github.com/stretchr/testify/require" ) @@ -39,6 +40,12 @@ func TestMaskSinkURIForError(t *testing.T) { require.Contains(t, err.Error(), "") require.Contains(t, err.Error(), `parse ""`) require.Contains(t, err.Error(), "invalid URL escape") + + err = genSinkURIInvalidError(invalidURI, perrors.Trace(mustParseURLError(t, invalidURI))) + require.NotContains(t, err.Error(), "verysecure") + require.Contains(t, err.Error(), "") + require.Contains(t, err.Error(), `parse ""`) + require.Contains(t, err.Error(), "invalid URL escape") } func mustParseURLError(t *testing.T, rawURL string) error { diff --git a/pkg/util/uri.go b/pkg/util/uri.go index c67badd5f5..fb249a96f6 100644 --- a/pkg/util/uri.go +++ b/pkg/util/uri.go @@ -14,6 +14,7 @@ package util import ( + stderrors "errors" "net" "net/url" "strings" @@ -124,13 +125,10 @@ func MaskSensitiveDataInURLError(err error) error { if err == nil { return nil } - urlErr, ok := err.(*url.Error) - if !ok { + var urlErr *url.Error + if !stderrors.As(err, &urlErr) { return err } - return &url.Error{ - Op: urlErr.Op, - URL: MaskSensitiveDataInURIForError(urlErr.URL), - Err: urlErr.Err, - } + urlErr.URL = MaskSensitiveDataInURIForError(urlErr.URL) + return err } diff --git a/pkg/util/uri_test.go b/pkg/util/uri_test.go index 1cd747428f..42bfe61f6d 100644 --- a/pkg/util/uri_test.go +++ b/pkg/util/uri_test.go @@ -17,6 +17,7 @@ import ( "net/url" "testing" + perrors "github.com/pingcap/errors" "github.com/stretchr/testify/require" ) @@ -38,4 +39,13 @@ func TestMaskSensitiveDataInURLError(t *testing.T) { require.NotContains(t, maskedErr.Error(), "verysecure") require.Contains(t, maskedErr.Error(), `parse ""`) require.Contains(t, maskedErr.Error(), "invalid URL escape") + + _, wrappedErr := url.Parse(rawURL) + require.Error(t, wrappedErr) + wrappedErr = perrors.Trace(wrappedErr) + + maskedErr = MaskSensitiveDataInURLError(wrappedErr) + require.NotContains(t, maskedErr.Error(), "verysecure") + require.Contains(t, maskedErr.Error(), `parse ""`) + require.Contains(t, maskedErr.Error(), "invalid URL escape") }