From f0a3681e1d0e2bcd9052ff8a6aa9616d416ad5da Mon Sep 17 00:00:00 2001 From: mattermost-code Date: Tue, 28 Jul 2026 08:29:21 -0400 Subject: [PATCH] Automated cherry pick of #37595 (#37709) Automatic Merge --- server/channels/app/channels.go | 3 +- server/channels/app/imaging/decode.go | 81 ++++++++++++++++++++ server/channels/app/imaging/decode_test.go | 87 ++++++++++++++++++++++ server/channels/app/team.go | 7 +- server/go.mod | 18 ++--- server/go.sum | 36 ++++----- 6 files changed, 201 insertions(+), 31 deletions(-) diff --git a/server/channels/app/channels.go b/server/channels/app/channels.go index df3e90c2e85..3b05b11150b 100644 --- a/server/channels/app/channels.go +++ b/server/channels/app/channels.go @@ -215,7 +215,8 @@ func NewChannels(s *Server) (*Channels, error) { decoderConcurrency = runtime.NumCPU() } ch.imgDecoder, imgErr = imaging.NewDecoder(imaging.DecoderOptions{ - ConcurrencyLevel: decoderConcurrency, + ConcurrencyLevel: decoderConcurrency, + MaxDecodedResolution: *ch.cfgSvc.Config().FileSettings.MaxImageResolution, }) if imgErr != nil { return nil, errors.Wrap(imgErr, "failed to create image decoder") diff --git a/server/channels/app/imaging/decode.go b/server/channels/app/imaging/decode.go index 87bf4d37a3c..95ed43826ef 100644 --- a/server/channels/app/imaging/decode.go +++ b/server/channels/app/imaging/decode.go @@ -4,6 +4,7 @@ package imaging import ( + "bytes" "errors" "fmt" "image" @@ -23,6 +24,13 @@ type DecoderOptions struct { // The level of concurrency for the decoder. This defines a limit on the // number of concurrently running encoding goroutines. ConcurrencyLevel int + + // MaxDecodedResolution, when greater than zero, is the maximum number of + // pixels (width*height) an image may declare before it is decoded. Images + // exceeding this limit are rejected up front. This is a defense-in-depth + // guard against decompression bombs that bounds server-side memory + // allocation regardless of the underlying codec's behavior. + MaxDecodedResolution int64 } func (o *DecoderOptions) validate() error { @@ -52,8 +60,76 @@ func NewDecoder(opts DecoderOptions) (*Decoder, error) { return &d, nil } +// enforceResolutionLimit inspects the image header and rejects images whose +// declared resolution exceeds the configured MaxDecodedResolution before any +// pixel data is decoded. It returns the reader to use for the subsequent full +// decode: seekable readers are rewound to their original position, while +// non-seekable readers are buffered so the cap is enforced for every input. +func (d *Decoder) enforceResolutionLimit(rd io.Reader) (io.Reader, error) { + if d.opts.MaxDecodedResolution <= 0 { + return rd, nil + } + + if seeker, ok := rd.(io.ReadSeeker); ok { + // Preserve the caller's position so an image decoded from a non-zero + // offset still lines up for the full decode. + start, err := seeker.Seek(0, io.SeekCurrent) + if err != nil { + return nil, fmt.Errorf("imaging: failed to read image position: %w", err) + } + cfg, _, cfgErr := image.DecodeConfig(seeker) + if _, err := seeker.Seek(start, io.SeekStart); err != nil { + return nil, fmt.Errorf("imaging: failed to seek after reading image config: %w", err) + } + if err := d.checkConfigResolution(cfg, cfgErr); err != nil { + return nil, err + } + return rd, nil + } + + // Non-seekable reader: buffer the input so the resolution cap can still be + // enforced and the data can be decoded afterwards. + data, err := io.ReadAll(rd) + if err != nil { + return nil, fmt.Errorf("imaging: failed to read image data: %w", err) + } + cfg, _, cfgErr := image.DecodeConfig(bytes.NewReader(data)) + if err := d.checkConfigResolution(cfg, cfgErr); err != nil { + return nil, err + } + return bytes.NewReader(data), nil +} + +// checkConfigResolution rejects a decoded image config whose resolution exceeds +// the configured cap. A config-decode error is ignored so the subsequent full +// decode can surface a meaningful error for malformed input. +func (d *Decoder) checkConfigResolution(cfg image.Config, cfgErr error) error { + if cfgErr != nil { + return nil + } + if exceedsResolution(int64(cfg.Width), int64(cfg.Height), d.opts.MaxDecodedResolution) { + return fmt.Errorf("imaging: image resolution %dx%d exceeds the maximum allowed %d pixels", cfg.Width, cfg.Height, d.opts.MaxDecodedResolution) + } + return nil +} + +// exceedsResolution reports whether width*height exceeds maxRes. It divides +// instead of multiplying so it can't overflow int64 for very large declared +// dimensions. +func exceedsResolution(width, height, maxRes int64) bool { + if width <= 0 || height <= 0 { + return false + } + return width > maxRes/height +} + // Decode decodes the given encoded data and returns the decoded image. func (d *Decoder) Decode(rd io.Reader) (img image.Image, format string, err error) { + rd, err = d.enforceResolutionLimit(rd) + if err != nil { + return nil, "", err + } + if d.opts.ConcurrencyLevel != 0 { d.sem <- struct{}{} defer func() { <-d.sem }() @@ -71,6 +147,11 @@ func (d *Decoder) Decode(rd io.Reader) (img image.Image, format string, err erro // must be called when access to the raw image is not needed anymore. // This sets the raw image data pointer to nil in an attempt to help the GC to re-use the underlying data as soon as possible. func (d *Decoder) DecodeMemBounded(rd io.Reader) (img image.Image, format string, releaseFunc func(), err error) { + rd, err = d.enforceResolutionLimit(rd) + if err != nil { + return nil, "", nil, err + } + if d.opts.ConcurrencyLevel != 0 { d.sem <- struct{}{} defer func() { diff --git a/server/channels/app/imaging/decode_test.go b/server/channels/app/imaging/decode_test.go index 5328f886e77..2a873a2c79a 100644 --- a/server/channels/app/imaging/decode_test.go +++ b/server/channels/app/imaging/decode_test.go @@ -5,6 +5,9 @@ package imaging import ( "bytes" + "image" + "image/png" + "io" "os" "sync" "testing" @@ -256,3 +259,87 @@ func TestDecoderDecodeMemBounded(t *testing.T) { require.Empty(t, d.sem) }) } + +// TestDecoderMaxDecodedResolution verifies the defense-in-depth cap: the shared +// decoder refuses to decode any image whose declared resolution exceeds the +// configured limit, regardless of the underlying codec, before allocating +// pixel data. +func TestDecoderMaxDecodedResolution(t *testing.T) { + makePNG := func(w, h int) []byte { + var buf bytes.Buffer + require.NoError(t, png.Encode(&buf, image.NewRGBA(image.Rect(0, 0, w, h)))) + return buf.Bytes() + } + + d, err := NewDecoder(DecoderOptions{MaxDecodedResolution: 100}) + require.NoError(t, err) + + t.Run("Decode rejects image exceeding the cap", func(t *testing.T) { + img, format, decErr := d.Decode(bytes.NewReader(makePNG(50, 50))) // 2500px > 100 + require.Error(t, decErr) + require.ErrorContains(t, decErr, "exceeds the maximum allowed") + require.Nil(t, img) + require.Empty(t, format) + }) + + t.Run("Decode allows image within the cap", func(t *testing.T) { + img, format, decErr := d.Decode(bytes.NewReader(makePNG(5, 5))) // 25px <= 100 + require.NoError(t, decErr) + require.NotNil(t, img) + require.Equal(t, "png", format) + }) + + t.Run("DecodeMemBounded rejects image exceeding the cap", func(t *testing.T) { + img, format, release, decErr := d.DecodeMemBounded(bytes.NewReader(makePNG(50, 50))) + require.Error(t, decErr) + require.ErrorContains(t, decErr, "exceeds the maximum allowed") + require.Nil(t, img) + require.Empty(t, format) + require.Nil(t, release) + }) + + t.Run("cap disabled by default", func(t *testing.T) { + dd, ddErr := NewDecoder(DecoderOptions{}) + require.NoError(t, ddErr) + img, _, decErr := dd.Decode(bytes.NewReader(makePNG(50, 50))) + require.NoError(t, decErr) + require.NotNil(t, img) + }) + + // A non-seekable reader must still be subject to the cap; the decoder + // buffers it internally rather than silently bypassing the check. + t.Run("cap enforced on non-seekable reader", func(t *testing.T) { + // io.MultiReader is not an io.ReadSeeker. + img, format, decErr := d.Decode(io.MultiReader(bytes.NewReader(makePNG(50, 50)))) + require.Error(t, decErr) + require.ErrorContains(t, decErr, "exceeds the maximum allowed") + require.Nil(t, img) + require.Empty(t, format) + }) + + t.Run("non-seekable reader within cap decodes from buffer", func(t *testing.T) { + img, format, decErr := d.Decode(io.MultiReader(bytes.NewReader(makePNG(5, 5)))) + require.NoError(t, decErr) + require.NotNil(t, img) + require.Equal(t, "png", format) + }) +} + +// TestExceedsResolution verifies the resolution comparison rejects over-limit +// images (including dimensions large enough to overflow a naive int64 +// multiplication) without wrapping around. +func TestExceedsResolution(t *testing.T) { + const maxRes = int64(7680 * 4320) // default 8K cap, ~33 MPx + + require.False(t, exceedsResolution(100, 100, maxRes)) + require.False(t, exceedsResolution(7680, 4320, maxRes)) // exactly at the cap + require.True(t, exceedsResolution(10000, 10000, maxRes)) + + // width*height here (2^80) overflows int64; the division-based check must + // still reject it rather than wrap to a small/negative value. + require.True(t, exceedsResolution(1<<40, 1<<40, maxRes)) + + // Non-positive dimensions are treated as not exceeding the cap. + require.False(t, exceedsResolution(0, 100, maxRes)) + require.False(t, exceedsResolution(100, 0, maxRes)) +} diff --git a/server/channels/app/team.go b/server/channels/app/team.go index 43f45183d16..a3da635293d 100644 --- a/server/channels/app/team.go +++ b/server/channels/app/team.go @@ -9,7 +9,6 @@ import ( "encoding/json" "errors" "fmt" - "image" "io" "mime/multipart" "net/http" @@ -2043,8 +2042,10 @@ func (a *App) SetTeamIconFromMultiPartFile(rctx request.CTX, teamID string, file } func (a *App) SetTeamIconFromFile(rctx request.CTX, team *model.Team, file io.ReadSeeker) *model.AppError { - // Decode image into Image object - img, format, err := image.Decode(file) + // Decode image into Image object using the shared decoder so team icons + // are subject to the same concurrency and resolution safeguards as other + // user-uploaded images. + img, format, err := a.ch.imgDecoder.Decode(file) if err != nil { return model.NewAppError("SetTeamIcon", "api.team.set_team_icon.decode.app_error", nil, "", http.StatusBadRequest).Wrap(err) } diff --git a/server/go.mod b/server/go.mod index 8e99d8f4073..c38f420d0dc 100644 --- a/server/go.mod +++ b/server/go.mod @@ -74,13 +74,13 @@ require ( github.com/wiggin77/merror v1.0.5 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c github.com/yuin/goldmark v1.8.2 - golang.org/x/crypto v0.51.0 - golang.org/x/image v0.40.0 - golang.org/x/net v0.54.0 - golang.org/x/sync v0.20.0 - golang.org/x/sys v0.44.0 - golang.org/x/term v0.43.0 - golang.org/x/text v0.37.0 + golang.org/x/crypto v0.53.0 + golang.org/x/image v0.44.0 + golang.org/x/net v0.56.0 + golang.org/x/sync v0.22.0 + golang.org/x/sys v0.46.0 + golang.org/x/term v0.44.0 + golang.org/x/text v0.40.0 gopkg.in/mail.v2 v2.3.1 ) @@ -216,8 +216,8 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect go4.org v0.0.0-20260112195520-a5071408f32f // indirect golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/tools v0.47.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260511170946-3700d4141b60 // indirect google.golang.org/grpc v1.81.0 // indirect google.golang.org/protobuf v1.36.11 // indirect diff --git a/server/go.sum b/server/go.sum index 1393f6f20b2..056e8e54bef 100644 --- a/server/go.sum +++ b/server/go.sum @@ -703,13 +703,13 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= -golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw= golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= -golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8= -golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -719,8 +719,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -747,8 +747,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -768,8 +768,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -805,8 +805,8 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -817,8 +817,8 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -830,8 +830,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= @@ -847,8 +847,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=