Description
Since April 2023, new S3 buckets are created with Object Ownership set to "Bucket owner enforced" by default, which disables ACLs entirely. AWS recommends using bucket policies instead of ACLs for access control.
The plugin currently always sends an ACL header with every upload (public-read or private depending on the makeUploadsPublic setting). On buckets with ACLs disabled, any request containing an ACL header is rejected by AWS with AccessControlListNotSupported.
This means there is no configuration in the plugin that allows it to work with modern S3 buckets without enabling ACLs on the bucket side (going against AWS recommendations).
Steps to reproduce
- Create an S3 bucket (new buckets default to
BucketOwnerEnforced / ACLs disabled)
- Configure the Craft CMS S3 filesystem pointing to this bucket
- Set
Make Uploads Public to either ON or OFF
- Try to upload any asset
Error
AWS HTTP error: AccessControlListNotSupported (client): The bucket does not allow ACLs
Full trace shows the error originates in FlysystemFs::writeFileFromStream → AwsS3V3Adapter:
{
"name": "Filesystem Error",
"message": "Unable to write stream to \"test-upload.png\"",
"exception": "craft\\errors\\FsException",
"file": "vendor/craftcms/cms/src/flysystem/src/base/FlysystemFs.php",
"line": 139
}
Root cause
In Fs::createAdapter() (src/Fs.php, line ~292), the adapter is always created with PortableVisibilityConverter:
return new AwsS3V3Adapter(
$client,
App::parseEnv($this->bucket),
$this->_subfolder(),
new PortableVisibilityConverter($this->visibility()), // ← always sends ACL
// ...
);
PortableVisibilityConverter maps visibility to ACL strings:
Visibility::PUBLIC → public-read
Visibility::PRIVATE → private
Both values cause putObject to include an ACL parameter, which is rejected when ACLs are disabled on the bucket.
The existing makeUploadsPublic toggle (added in PR #50) only switches between ACL values — there is no option to not send ACL at all.
Proposed solution
Add a new setting disableAcl.
Environment
- Craft CMS: 5.x
- Plugin version: 2.3.0
- PHP: 8.1+
- Flysystem: 3.x
Description
Since April 2023, new S3 buckets are created with Object Ownership set to "Bucket owner enforced" by default, which disables ACLs entirely. AWS recommends using bucket policies instead of ACLs for access control.
The plugin currently always sends an ACL header with every upload (
public-readorprivatedepending on themakeUploadsPublicsetting). On buckets with ACLs disabled, any request containing an ACL header is rejected by AWS withAccessControlListNotSupported.This means there is no configuration in the plugin that allows it to work with modern S3 buckets without enabling ACLs on the bucket side (going against AWS recommendations).
Steps to reproduce
BucketOwnerEnforced/ ACLs disabled)Make Uploads Publicto either ON or OFFError
Full trace shows the error originates in
FlysystemFs::writeFileFromStream→AwsS3V3Adapter:{ "name": "Filesystem Error", "message": "Unable to write stream to \"test-upload.png\"", "exception": "craft\\errors\\FsException", "file": "vendor/craftcms/cms/src/flysystem/src/base/FlysystemFs.php", "line": 139 }Root cause
In
Fs::createAdapter()(src/Fs.php, line ~292), the adapter is always created withPortableVisibilityConverter:PortableVisibilityConvertermaps visibility to ACL strings:Visibility::PUBLIC→public-readVisibility::PRIVATE→privateBoth values cause
putObjectto include anACLparameter, which is rejected when ACLs are disabled on the bucket.The existing
makeUploadsPublictoggle (added in PR #50) only switches between ACL values — there is no option to not send ACL at all.Proposed solution
Add a new setting
disableAcl.Environment