SN-2026-023: Authenticated STREAMING-UNSIGNED-PAYLOAD-TRAILER Replay Window and Missing SigV4 Condition Keys
Status: Fixed on main (14ecb222e, 296c9710b); scheduled for the next post-20261109 Server release
Identifier: SN-2026-023 (Security Advisory Ledger, #338)
Severity: High
Affected scope: SILO Server (RELEASE.2026-11-09T00-00-00Z and earlier) and inherited upstream MinIO releases since 2023-05-05 (76913a9fd)
Unaffected components: silo-console, mcli, silo-pkg, and kes
AWS S3’s STREAMING-UNSIGNED-PAYLOAD-TRAILER mode signs the initial HTTP request headers using Signature Version 4 (Authorization: AWS4-HMAC-SHA256 ...) while transmitting aws-chunked body chunks and trailing checksums without per-chunk cryptographic signatures. Because the canonical request binds the literal token "STREAMING-UNSIGNED-PAYLOAD-TRAILER" rather than a SHA-256 digest of the payload, bounding the validity window of the signed X-Amz-Date header (globalMaxSkewTime = 15 minutes) is the sole time bound preventing old captured request headers from being replayed.
Inherited from upstream MinIO, setAuthMiddleware (cmd/auth-handler.go) enforced parseAmzDateHeader and globalMaxSkewTime on authTypeSigned, authTypeStreamingSigned, and authTypeStreamingSignedTrailer, omitting authTypeStreamingUnsignedTrailer. Any captured header block from an authenticated unsigned-trailer upload remained accepted indefinitely—hours, days, or months later, for as long as the access key remained active—and could be paired with arbitrary replacement payload bytes of the same X-Amz-Decoded-Content-Length. In addition, getConditionValuesWithTags (cmd/bucket-policy.go) left s3:signatureversion and s3:authType unpopulated for both trailer streaming modes.
Summary
- What went wrong:
- Unbounded replay with arbitrary same-length body substitution: Neither
setAuthMiddlewarenordoesSignatureMatch(cmd/signature-v4.go) checked clock skew onauthTypeStreamingUnsignedTrailer. Once anAuthorization-authenticatedSTREAMING-UNSIGNED-PAYLOAD-TRAILERheader set was logged, proxied, or intercepted, an attacker could replay those exact headers at any future date and stream completely different content of equal decoded length to overwrite the target(bucket, key)(orPutObjectPart/PutObjectExtracttarget). - Missing SigV4 policy condition keys on trailer streams:
getConditionValuesWithTagspopulateds3:signatureversion = "AWS4-HMAC-SHA256"ands3:authType = "REST-HEADER"for legacyauthTypeStreamingSigned, but left both values empty forauthTypeStreamingSignedTrailerand authenticatedauthTypeStreamingUnsignedTrailer. AnAllowrequiring those values could reject legitimate trailer uploads, while aDenyrequiring them could fail to match.
- Unbounded replay with arbitrary same-length body substitution: Neither
- Upstream provenance: Introduced in upstream MinIO commit
76913a9fd(2023-05-05, minio/minio#16484, “Signed trailers for signature v4”), and left unaddressed when upstream later added signature verification for unsigned-trailer readers in8c7097528(2025-04-03, minio/minio#21103). - Fix: Commits
14ecb222eand296c9710benforce the 15-minuteX-Amz-Dateskew window insetAuthMiddlewarewheneverauthTypeStreamingUnsignedTrailercarries anAuthorizationheader (keeping anonymous public-bucket streaming uploads intact), verify header signatures andcheckUnsignedHeaderscoverage across streaming seed signatures, and populate the exact AWS S3s3:signatureversionands3:authTypecondition values for both trailer streaming modes.
1. Upstream Provenance and Root Cause
Why authTypeStreamingUnsignedTrailer Missed the Skew Gate
In SILO and upstream MinIO, Signature Version 4 header validation splits responsibilities between two layers:
doesSignatureMatch(cmd/signature-v4.go): ParsesX-Amz-Date(orDate) solely to derive theYYYYMMDDsigning key scope and verify the HMAC over the canonical request. It assumes the HTTP middleware already enforcedglobalMaxSkewTime(15 * time.Minute).setAuthMiddleware(cmd/auth-handler.go): CallsparseAmzDateHeader(r)and rejects requests whose timestamp differs fromUTCNow()by more thanglobalMaxSkewTimewithErrRequestTimeTooSkewed.
When upstream added trailer-based streaming modes in 76913a9fd (2023-05-05), getRequestAuthType classified any request carrying X-Amz-Content-Sha256: STREAMING-UNSIGNED-PAYLOAD-TRAILER—both signed requests with an Authorization header and anonymous uploads to public buckets—under a single enum value authTypeStreamingUnsignedTrailer. Because anonymous uploads have no X-Amz-Date header, upstream added only authTypeStreamingSignedTrailer to the skew-check switch in setAuthMiddleware:
When upstream subsequently patched unsigned-trailer authentication in 8c7097528 (2025-04-03, and later SILO fixes CVE-2026-41145 and CVE-2026-40344), newUnsignedV4ChunkedReader called reqSignatureV4Verify -> doesSignatureMatch, which verified the header HMAC without checking clock skew. As a result, an X-Amz-Date from years in the past (or future) passed authentication cleanly.
Why is an unbounded replay window uniquely severe on STREAMING-UNSIGNED-PAYLOAD-TRAILER? For a request whose signed X-Amz-Content-Sha256 contains the actual payload digest, that digest binds the body bytes. This does not apply to payload modes whose canonical request uses an unsigned-payload token. On STREAMING-UNSIGNED-PAYLOAD-TRAILER, only Host, X-Amz-Content-Sha256: STREAMING-UNSIGNED-PAYLOAD-TRAILER, X-Amz-Date, X-Amz-Decoded-Content-Length, and X-Amz-Trailer are signed—neither the chunk bytes nor the trailing CRC/checksum value is covered by the SigV4 signature. Anyone who obtains a captured header block (from middlebox logs, debug traces, or cleartext HTTP traffic) can compute a fresh valid CRC trailer for any replacement payload of matching byte length and overwrite the object at any point in the future.
Omitted SigV4 Condition Keys in getConditionValuesWithTags
In cmd/bucket-policy.go, getConditionValuesWithTags populated s3:signatureversion and s3:authType via a switch getRequestAuthType(r) block written before trailer streaming existed (authTypeSigned, authTypePresigned, authTypeStreamingSigned). Neither authTypeStreamingSignedTrailer nor authenticated authTypeStreamingUnsignedTrailer was added in 76913a9fd. Because modern AWS SDKs (Go v2, Java v2, Python boto3, JS v3) enable default CRC32/CRC64NVME trailing checksums on PutObject and UploadPart, their requests routinely arrive as authTypeStreamingSignedTrailer or authTypeStreamingUnsignedTrailer—evaluating with empty s3:signatureversion and s3:authType values.
2. Impact Scope
| Surface / Configuration | Concrete Impact |
|---|---|
PutObject, PutObjectPart, PutObjectExtract |
Captured authenticated STREAMING-UNSIGNED-PAYLOAD-TRAILER request headers remain replayable forever while the access key is active, allowing arbitrary same-length payload overwrites (unless blocked by Object Lock on the destination). |
Policies conditioned on s3:signatureversion / s3:authType |
Rules requiring AWS4-HMAC-SHA256 / REST-HEADER fail to match trailer uploads when those condition values are empty. |
Anonymous public-bucket aws-chunked uploads |
Unaffected before and after the fix (no Authorization header is sent). |
3. Solution Design: Why It Is Necessary and Sufficient
Commits 14ecb222e and 296c9710b close these gaps at the protocol classification and streaming signature boundaries:
- Conditional skew enforcement in
setAuthMiddleware(cmd/auth-handler.go): WhenaType == authTypeStreamingUnsignedTrailerandr.Header.Get(xhttp.Authorization) != "", the middleware appliesparseAmzDateHeader(r)and the 15-minute skew check before invoking the handler or chunk reader. A missing date returns400 AccessDenied, a malformed date returns400 MalformedDate, and a timestamp outside the window returns403 RequestTimeTooSkewed. WithoutAuthorization, the request remains anonymous and must still pass bucket policy authorization innewUnsignedV4ChunkedReader. - Complete SigV4 condition population in
getConditionValuesWithTags(cmd/bucket-policy.go):authTypeStreamingSignedTrailer: setss3:signatureversion = "AWS4-HMAC-SHA256"ands3:authType = "REST-HEADER".authTypeStreamingUnsignedTrailerwhenr.Header.Get(xhttp.Authorization) != "": setss3:signatureversion = "AWS4-HMAC-SHA256"ands3:authType = "REST-HEADER".authTypeStreamingUnsignedTrailerwithoutAuthorization: leaves both condition values empty, so an anonymous request cannot match the SigV4 signature version orREST-HEADERauthentication type.
Sufficiency and Necessity
- Necessary: Enforcing
parseAmzDateHeaderunconditionally on allauthTypeStreamingUnsignedTrailerrequests (without checkingAuthorization != "") would break unauthenticatedaws-chunkeduploads to buckets configured for anonymous public writes (newUnsignedV4ChunkedReaderexplicitly supports anonymous bucket-policy uploads, tested incmd/streaming-auth-boundary_test.go). Conversely, gating onAuthorization != ""covers every credentialedSTREAMING-UNSIGNED-PAYLOAD-TRAILERupload because presigned query-string authentication withSTREAMING-UNSIGNED-PAYLOAD-TRAILERis already forbidden (CVE-2026-41145). - Sufficient: Enforcing the skew window in
setAuthMiddlewareprotects every handler that acceptsauthTypeStreamingUnsignedTrailer(PutObjectHandler,PutObjectPartHandler,PutObjectExtractHandler, and any custom S3 route) from a single entry point before body bytes are read.
4. Trade-Offs, Caveats, and Operational Costs
| Dimension | Engineering Trade-Off & Residual Boundary |
|---|---|
| Client clock synchronization | Authenticated clients using STREAMING-UNSIGNED-PAYLOAD-TRAILER whose system clocks drift by more than 15 minutes from server UTC are now rejected with RequestTimeTooSkewed, matching all other SigV4 modes and AWS S3. |
Policies using s3:authType allowlists |
Authenticated signed-trailer and unsigned-trailer requests both use REST-HEADER. An allowlist already containing that value needs no trailer-specific additions. Use s3:x-amz-content-sha256 to distinguish payload modes. |
| Inherent 15-minute window over plaintext HTTP | The unsigned-trailer body has no per-chunk cryptographic signatures, even within the valid time window. Use TLS. To reject this payload mode, add a Deny for s3:PutObject with StringEquals on s3:x-amz-content-sha256 equal to STREAMING-UNSIGNED-PAYLOAD-TRAILER. This condition rejects this specific mode; it does not prohibit every other unsigned-payload mode. |
Upgrade and Policy Hardening Guidance
- Upgrade SILO Server to the post-
20261109release containing14ecb222eand296c9710b. - Always terminate S3 traffic over TLS so
Authorizationheaders andSTREAMING-UNSIGNED-PAYLOAD-TRAILERchunk streams cannot be captured or modified in transit, and ensure HTTP reverse proxies or debug loggers redact theAuthorizationheader. - Optional policy hardening (disabling unsigned-trailer payloads): Add an IAM or bucket-policy
Denyfors3:PutObjectwithStringEquals: {"s3:x-amz-content-sha256": "STREAMING-UNSIGNED-PAYLOAD-TRAILER"}. The mode token is a payload condition value; authenticated trailer requests haves3:authType = "REST-HEADER". Verify the intended policy with your upload clients.
See the Security Advisory Ledger, CVE-2026-41145, CVE-2026-40344, and Signed Header Coverage Design (SN-2026-011).