fix(storage): flip remaining S1 S3 S4 S5 S6 storage contracts - #3417
Merged
Conversation
Empty and slash-only keys must throw InvalidKey. LocalDisk url() and signedUrl() must encode spaces and reserved characters. S3Disk.delete() must HEAD then return false when missing. put() must send a signed x-amz-acl. expiresIn outside 1..604800 must throw on both disks. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
LocalDisk $resolve throws InvalidKey for empty and slash-only keys. url() and signedUrl() encode like S3. S3Disk.delete() HEADs then returns false on a miss. put() sends a signed x-amz-acl. expiresIn outside 1..604800 throws InvalidExpiresIn on both disks. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
The signedHeaders header list is now built from range and acl. \$assertSuccess already fails a non-2xx DELETE. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Peter flipped the five Storage holds that #3416 left unflipped. Empty and slash-only keys concatenated onto
rootandget()threwNotFound. LocalDiskurl()andsignedUrl()left spaces raw while S3 encoded them. S3delete()returned true after any 2xx.put()never sent an ACL.expiresInaccepted non-positive and over-max values.This PR flips S1, S3, S4, S5, and S6 only. Desk IDs stay S1–S8. S2, S7, and S8 stay as they landed on develop in #3416.
Scope
vendor/wheels/storage/drivers/LocalDisk.cfc$resolvethrowsWheels.Storage.InvalidKeywhen the key is empty or slash-only$joinUrlRFC3986-encodes the key path (+→%20,*→%2A,%7E→~, slashes kept)signedUrlthrowsWheels.Storage.InvalidExpiresInoutside1..604800vendor/wheels/storage/drivers/S3Disk.cfcdelete()callsexists()(HEAD) and returns false on a missput()sends a signedx-amz-acl(public→public-read, default/private→private)vendor/wheels/storage/S3Signer.cfcsignedHeaders()takes optionalacl. Emptyacland emptyrangekeep the S8 canonical requestpresignGetUrl()throwsWheels.Storage.InvalidExpiresInoutside1..604800vendor/wheels/tests/specs/storage/StorageSpec.cfcvendor/wheels/tests/_assets/storage/S3DiskDeleteStub.cfc— HEAD/DELETE/PUT stub withseed()andlastRequest()changelog.d/storage-hardener-s1-s3-s6.changed.mdandstorage-hardener-s1-s5.security.mdOut: leading-slash leftover (
$objectPathltrims/,S3Signerdoes not). Policy leftovers stay closed. S2 resolver, S7 emptysigningKey, and S8 header-auth vector are unchanged.Tradeoffs
expiresInthrows. Clamp would hide a bad caller. AWS SigV4 rejectsX-Amz-Expiresabove 604800. The oldexpiresIn = -10expired-URL trick is gone. Expiry is proven withverifySignatureand a past epoch.visibility=publicmaps tox-amz-acl: public-read. Raw AWS ACL names pass through. The ACL header is signed so S3 does not returnSignatureDoesNotMatch.delete()reusesexists()for the HEAD. A 5xx or connection failure still throwsWheels.Storage.RequestFailedinstead of returning false.Blast Radius
LocalDisk.$resolve("", "/", "///")now throws. Apps that stored an empty key as the root directory fail closed.put("")cannot write the disk root.S3
delete()of a missing key returns false. Callers that treated every 2xx delete as true will see false on a miss.Signed URLs with
expiresIn <= 0or> 604800throw on both disks.expiresIn = 86400(the AWS query-auth vector) still works.Desk S1–S8
$resolve("", "/", "///")andput("")throwWheels.Storage.InvalidKey. No root write.Find("..")only.url("my docs/q3 report.pdf")is/uploads/my%20docs/q3%20report.pdf.signedUrlencodes the same path. Token still verifies against the raw key.delete("gone.txt")is false. Seeded object deletes and returns true. Second delete is false.put()sends signedx-amz-acl: private.visibility=publicsendspublic-read. S8 no-acl vector unchanged.Wheels.Storage.InvalidExpiresInfor0,-10, and604801. Bounds1and604800still sign.verifySignaturewith nosigningKeyreturns false.Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41.Verification
Commands actually run:
Scope:
wheels.tests.specs.storageRed (spec-only HEAD
3f89a9ac11b5f383ba4f3c42b9211c15e6b7e7b3):S1 empty-key throw, S3 encode, both S6 expiresIn throws, S4 HEAD-then-false, S5 ACL, and the seeded-delete follow-up all failed against the #3416 pins.
Green (this HEAD):
JSON from the same filter (
directory=wheels.tests.specs.storage):Wheels CLI 4.0.6. Lucee 7.0.0.395. sqlite.
Adobe and BoxLang were not run here.
HEAD
33dd695b28e5ee287e99ecc027d719ecda2f5f22Base
80d3e0fb4b1b8e670716716aeb2d3e56160668d9(develop after #3416).No closer keywords. Do not merge.