diff --git a/cmd/api-errors-checksum_test.go b/cmd/api-errors-checksum_test.go
new file mode 100644
index 000000000..3b1de75b5
--- /dev/null
+++ b/cmd/api-errors-checksum_test.go
@@ -0,0 +1,45 @@
+// Copyright (c) 2026 PGSTY
+//
+// This file is part of SILO Object Storage.
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/minio/minio/internal/hash"
+)
+
+func TestChecksumMismatchAPIMessage(t *testing.T) {
+ for _, algorithm := range []string{"CRC32", "CRC32C", "SHA1", "SHA256", "CRC64NVME", ""} {
+ t.Run(algorithm, func(t *testing.T) {
+ mismatch := hash.ChecksumMismatch{Algorithm: algorithm, Want: "expected", Got: "actual"}
+ want := errorCodes.ToAPIErr(ErrContentChecksumMismatch).Description
+ if algorithm != "" {
+ want = fmt.Sprintf("The %s you specified did not match the calculated checksum.", algorithm)
+ }
+ for _, err := range []error{mismatch, fmt.Errorf("upload: %w", mismatch)} {
+ got := toAPIError(t.Context(), err)
+ if got.HTTPStatusCode != http.StatusBadRequest || got.Code != "XAmzContentChecksumMismatch" || got.Description != want {
+ t.Fatalf("toAPIError(%v) = %+v, want 400 XAmzContentChecksumMismatch: %s", err, got, want)
+ }
+ }
+ })
+ }
+}
diff --git a/go.mod b/go.mod
index 3a25fe190..c22798612 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.27.2
// Console and MC retain their historical module paths for best-effort upstream
// compatibility. Pin the maintained PGSTY implementations used by SILO.
-replace github.com/minio/console => github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d
+replace github.com/minio/console => github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee
replace github.com/minio/mc => github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a
diff --git a/go.sum b/go.sum
index 1b8f71b99..84b5f5a3a 100644
--- a/go.sum
+++ b/go.sum
@@ -547,8 +547,8 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a h1:FY1vcTQT67HGBe23ozlhOiQnWCzUCGmeSN5x6DT/kFs=
github.com/pgsty/mc v0.0.0-20260916070421-e952aa78f10a/go.mod h1:6z9lX8kOvaWz2vLlP7BjjF3JWHnIH86XDA2HwcmpZH8=
-github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d h1:A9ou1GkUckGZd3BOgEXeZ393nbYST967SKcI4Lgr/Vs=
-github.com/pgsty/silo-console v0.0.0-20260916075814-1360e26d976d/go.mod h1:JS165CF2yoTBIx1X8k1IQ6vunJcZFG3pRZlWHJ8UT8o=
+github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee h1:e51AnrGe0Zq+nHTYaGfv/GkehQMO9Mekuw/ew6zYwOM=
+github.com/pgsty/silo-console v0.0.0-20261015132217-e5b165e4c0ee/go.mod h1:jpt39bx4wjhXoFB+BTOa/bx/pmU3pXzp2cT7PnT21Lo=
github.com/pgsty/silo-pkg/v3 v3.14.1 h1:8MONT3Hky9EOnsuPk29590pEBE6MILaOnJtaejPPi7M=
github.com/pgsty/silo-pkg/v3 v3.14.1/go.mod h1:jxKWxi52jSsDaDhXXPqjOyPDYdxx2iqdBXyLfM/xSIw=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
diff --git a/internal/hash/checksum-errors_test.go b/internal/hash/checksum-errors_test.go
new file mode 100644
index 000000000..e280318c4
--- /dev/null
+++ b/internal/hash/checksum-errors_test.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2026 PGSTY
+//
+// This file is part of SILO Object Storage.
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package hash
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestChecksumMismatchAlgorithm(t *testing.T) {
+ content := []byte("actual content")
+ for _, typ := range []ChecksumType{ChecksumCRC32, ChecksumCRC32C, ChecksumSHA1, ChecksumSHA256, ChecksumCRC64NVME} {
+ t.Run(typ.String(), func(t *testing.T) {
+ wrong := NewChecksumFromData(typ, []byte("wrong content"))
+ check := func(t *testing.T, err error) {
+ t.Helper()
+ var mismatch ChecksumMismatch
+ if !errors.As(err, &mismatch) || mismatch.Algorithm != typ.String() {
+ t.Fatalf("error=%+v, want %s checksum mismatch", err, typ)
+ }
+ }
+ check(t, wrong.Matches(content, 0))
+ parts := NewChecksumFromData(typ, content)
+ parts.WantParts = 2
+ check(t, parts.Matches(content, 1))
+ for _, mode := range []string{"header", "trailer", "malformed-trailer"} {
+ t.Run(mode, func(t *testing.T) {
+ reader, err := NewReader(t.Context(), bytes.NewReader(content), int64(len(content)), "", "", int64(len(content)))
+ if err != nil {
+ t.Fatal(err)
+ }
+ req := httptest.NewRequest(http.MethodPut, "http://localhost/bucket/object", nil)
+ if mode == "header" {
+ req.Header.Set(typ.Key(), wrong.Encoded)
+ } else {
+ req.Header.Set("x-amz-trailer", typ.Key())
+ req.Trailer = make(http.Header)
+ value := wrong.Encoded
+ if mode == "malformed-trailer" {
+ value = "!invalid-base64!"
+ }
+ req.Trailer.Set(typ.Key(), value)
+ }
+ if err = reader.AddChecksum(req, false); err != nil {
+ t.Fatal(err)
+ }
+ _, err = io.ReadAll(reader)
+ check(t, err)
+ })
+ }
+ })
+ }
+}