This check flags GCP Storage buckets that still allow legacy object-level ACLs instead of uniform bucket-level access. Mixing IAM and per-object ACLs makes permissions impossible to reason about and is a common cause of accidental public exposure. Fix it with one command: gcloud storage buckets update gs://BUCKET --uniform-bucket-level-access.
Google Cloud Storage supports two ways of controlling who can read and write objects: IAM policies and legacy access control lists (ACLs). When a bucket allows both, you end up with two overlapping permission systems that can contradict each other. Uniform bucket-level access turns off the ACL path and forces every permission decision through IAM. The storage_nouniformaccess check fires when a bucket still has uniform bucket-level access disabled, meaning object ACLs are still in play.
This is one of those misconfigurations that looks harmless until someone uploads an object with allUsers:READER on the ACL and your bucket IAM policy never gets a say. Below is what the check looks for, why it matters, and exactly how to clear it.
What this check detects
The check inspects each Cloud Storage bucket and reads its iamConfiguration.uniformBucketLevelAccess.enabled flag. If that flag is false, the bucket is using fine-grained access, which means individual objects can carry their own ACLs that grant access outside of IAM. The check reports the bucket as non-compliant.
Note: GCP calls the two modes "Uniform" and "Fine-grained." Fine-grained is the default for buckets created through some older tooling and APIs, which is why long-lived projects often have a mix. Uniform mode does not delete existing ACLs, it just stops them from being evaluated.
You can check a single bucket yourself:
gcloud storage buckets describe gs://my-bucket \
--format="default(uniform_bucket_level_access)"
A compliant bucket returns uniform_bucket_level_access.enabled: true.
Why it matters
The real problem with fine-grained access is not that ACLs are inherently insecure, it is that you now have two sources of truth. An auditor reading your bucket IAM policy might conclude the bucket is locked down, while a single object inside it is world-readable through an ACL that the IAM policy never references.
Here is the scenario that bites teams. An application writes objects to a bucket and, somewhere in the upload code or an old script, sets a predefined ACL like publicRead. The bucket IAM policy is clean. Security scans that only look at bucket-level IAM see nothing wrong. Meanwhile every object that pipeline writes is publicly accessible. Data leaks discovered this way have exposed customer records, backups, and credentials.
Warning: Object ACLs do not show up in the bucket IAM policy. If you only audit IAM bindings, you can miss public objects entirely. This is precisely why uniform access exists, and why this check is worth resolving rather than suppressing.
There are practical reasons beyond exposure too:
- Auditability. With uniform access, the bucket IAM policy is the complete and only answer to "who can access this data."
- IAM Conditions and VPC Service Controls. Several advanced controls assume IAM is the single decision point. Mixed-mode buckets can behave unexpectedly with these features.
- Compliance. CIS Google Cloud Foundations Benchmark recommends uniform bucket-level access. PCI DSS and SOC 2 reviewers routinely flag buckets that allow per-object ACLs.
How to fix it
Before flipping the switch, audit existing object ACLs. Uniform access ignores ACLs entirely, so any access currently granted only through an ACL will break the moment you enable it. Find out who relies on ACL-based access first.
Step 1: Inventory ACL-based grants
List the bucket's default object ACL and spot-check objects:
# Default ACL applied to new objects
gcloud storage buckets describe gs://my-bucket \
--format="default(default_object_acl)"
# ACL on a specific object
gcloud storage objects describe gs://my-bucket/path/to/object \
--format="default(acl)"
If you find grants like allUsers or allAuthenticatedUsers, decide whether that access is intentional. If it is, you will need to recreate it as an IAM binding before enabling uniform access.
Step 2: Recreate needed access as IAM bindings
For example, if objects were intentionally public via ACL, grant the equivalent at the bucket level through IAM:
gcloud storage buckets add-iam-policy-binding gs://my-bucket \
--member=allUsers \
--role=roles/storage.objectViewer
Danger: Granting allUsers the objectViewer role makes the entire bucket public. Only do this if the data is genuinely meant to be public, such as static website assets. For everything else, scope access to specific service accounts or groups instead.
Step 3: Enable uniform bucket-level access
gcloud storage buckets update gs://my-bucket \
--uniform-bucket-level-access
Verify the change:
gcloud storage buckets describe gs://my-bucket \
--format="default(uniform_bucket_level_access)"
Note: After enabling uniform access, you have a 90 day window during which you can switch back to fine-grained access. After 90 days the change is permanent and ACLs cannot be re-enabled on that bucket. Plan to validate your applications well within that window.
Console steps
- Open Cloud Storage in the GCP Console and select the bucket.
- Go to the Permissions tab.
- Under Access control, click Switch to uniform.
- Confirm the change.
Terraform
If you manage buckets as code, set uniform_bucket_level_access to true:
resource "google_storage_bucket" "data" {
name = "my-bucket"
location = "US"
uniform_bucket_level_access = true
# Recommended companions
public_access_prevention = "enforced"
}
Tip: Pair uniform access with public_access_prevention = "enforced". Uniform access removes ACLs from the equation, and enforced public access prevention guarantees no IAM binding can ever make the bucket public. Together they close both exposure paths.
How to prevent it from happening again
Fixing one bucket is easy. Keeping every new bucket compliant across dozens of projects is the real work. A few layers help.
Enforce with an Organization Policy
GCP ships a constraint that makes uniform bucket-level access mandatory for all new buckets in scope. Apply it at the organization or folder level:
gcloud resource-manager org-policies enable-enforce \
storage.uniformBucketLevelAccess \
--organization=ORGANIZATION_ID
Once this constraint is enforced, any attempt to create a fine-grained bucket is rejected at the API level, no matter what tool requested it.
Warning: Enforcing this constraint only affects buckets created after it is applied. Existing fine-grained buckets keep working as-is. Run a one-time sweep to remediate legacy buckets, then enforce the policy to prevent regressions.
Block it in CI/CD with policy-as-code
Catch the problem before it reaches the API. If you use Terraform, add a Conftest or OPA policy that fails any plan creating a bucket without uniform access:
package terraform.gcs
deny[msg] {
resource := input.resource_changes[_]
resource.type == "google_storage_bucket"
not resource.change.after.uniform_bucket_level_access
msg := sprintf("Bucket '%s' must enable uniform_bucket_level_access", [resource.address])
}
Wire that check into your pull request pipeline so a non-compliant bucket never gets merged.
Continuously monitor with Lensix
Organization policies and CI gates cover the paths you control. Continuous scanning covers everything else, including buckets created by third-party tooling, console clicks, or scripts that bypass your pipeline. Lensix runs storage_nouniformaccess against your live environment so any new fine-grained bucket surfaces quickly, with the offending bucket named and the fix ready to apply.
Best practices
- Default to uniform access for every new bucket. There is rarely a good reason to choose fine-grained access today. Make uniform the standard in your modules and templates.
- Combine it with public access prevention. Uniform access and enforced public access prevention together eliminate both ACL-based and IAM-based public exposure.
- Audit before enabling on legacy buckets. Always inventory object ACLs first so you do not silently break a workflow that depended on ACL grants.
- Use IAM Conditions for granular access. If you switched to fine-grained access in the past to scope access to a path prefix, IAM Conditions can express that intent without ACLs.
- Validate within the 90 day window. Treat the reversible period as a test phase, then let it become permanent once you confirm everything works.
- Centralize bucket creation. The fewer ways buckets get created in your org, the fewer chances for a non-compliant one to slip through.
Uniform bucket-level access is a small flag with a big payoff. It collapses two confusing permission systems into one, makes your storage genuinely auditable, and removes a whole class of accidental public exposure. Enable it everywhere, enforce it at the org level, and let continuous scanning catch the stragglers.

