Skip to content

fix(s3): conditional write and delete preconditions were never evaluated - #107

Merged
deblasis merged 3 commits into
mainfrom
pr/104-conditionals
Oct 1, 2026
Merged

deblasis merged 3 commits into
mainfrom
pr/104-conditionals

Conversation

@deblasis

@deblasis deblasis commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #104.

Mutations carrying preconditions succeeded unconditionally. All three of these returned success where S3 returns 412:

Request Before Expected
PUT with If-None-Match: * on an existing key 200 412
PUT with a non-matching If-Match 200 412
DELETE with a non-matching If-Match 204 412

Clients saw writes land that they had explicitly asked the server to reject. Found by @jeremydixon22 driving the aws-s3-style adapter with AWSSDK.S3 4.0.102.4, and reproduced with a hand-signed raw HTTP request carrying no SDK at all.

If-Match, If-None-Match, If-Modified-Since, and If-Unmodified-Since appear nowhere under adapters/aws-s3-style/. on_put_object, on_get_object, on_head_object, and on_delete_object branched on existence alone, and conformance/matrix.yaml did not record the gap.

What changed

Preconditions are evaluated after auth, bucket, and object lookup, in fail-closed RFC 7232 order: If-Match, If-Unmodified-Since, If-None-Match, If-Modified-Since.

  • GET and HEAD evaluate all four, comparing timestamps against a seconds-truncated Last-Modified. A matching If-None-Match or If-Modified-Since returns 304 with an empty body, as RFC 7232 requires; any other failed precondition returns 412 PreconditionFailed XML carrying <Condition> and <Key>.
  • PUT and DELETE evaluate the ETag conditions only, matching S3 conditional writes, so DELETE on a missing object stays idempotent at 204 while DELETE with If-Match on a missing object returns 412.
  • If-None-Match suppresses If-Modified-Since and If-Match suppresses If-Unmodified-Since.
  • Malformed or empty validators are ignored rather than rejected, and an unparseable date never produces a 400. The RFC 1123 parser accepts the GMT/UTC/UT forms real clients send and rejects two-digit years, numeric offsets, and impossible dates such as Feb 30.
  • Weak validators (W/"...") compare as strong, recorded as a deviation.

Two pre-existing bucket behaviors were left alone and are now documented as deviations in conformance/matrix.yaml: DELETE against a missing bucket returns 204, and GET/HEAD against a missing bucket returns NoSuchKey. Real S3 returns NoSuchBucket for both. Correcting those is a separate change.

Verification

$ env -u GOROOT go test -race ./internal/engine -run 'TestAwsS3|TestAWSS3' -v
--- PASS: TestAwsS3StyleAdapter
--- PASS: TestAwsS3StyleSigV4Verification
--- PASS: TestAwsS3StyleMultipartUpload
--- PASS: TestAWSS3StyleBinaryRoundTrip
--- PASS: TestAWSS3Conditionals

TestAWSS3Conditionals covers the three preconditions above plus read-side 304/412, missing-object precedence per operation, the RFC 7232 ignore rules, W/ and * and quoted validators, and the date grammar including leap years and Feb 30.

Through AWSSDK.S3 4.0.102.4 and a hand-signed raw HTTP request:

{ "createConflictHttpStatus": 412, "updateConflictHttpStatus": 412, "deleteConflictHttpStatus": 412 }

Two shared conformance cases that previously failed now pass: EnforcesWritePreconditions and DeletesObjectsIdempotentlyAndEnforcesPreconditions.

stunt adapter lint adapters/aws-s3-style is clean and just conformance-matrix regenerates with no drift.

@deblasis
deblasis merged commit d23d7e8 into main Oct 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

S3 conditional write and delete preconditions were never evaluated

1 participant