Why this matters
A Cloud Storage bucket in Google Cloud is private by default. The trouble starts when someone adds a public IAM binding, flips a legacy object ACL, or turns a bucket into a static website and forgets to remove the setting later. The bucket stays reachable at the public Cloud Storage endpoint, and anyone with the link can read the file. Bucket names are not secret. People guess them, and scanners try common names.
Public read is only one problem. A public write binding lets strangers upload objects. That can put malware on a download page, fill your bill with storage and egress charges, or overwrite a file your app trusts. Public object listings also expose file names, which can reveal customer names, project codes, or backup schedules.
The goal is not to make every bucket private on sight. Some buckets serve public assets on purpose. The job is to find every public path, decide which ones are intended, and close the rest with a guardrail that stops the same mistake next month.
Before you start
You need read access to every project that can hold a bucket. For discovery, roles/viewer at the organization or folder level works well, plus roles/cloudasset.viewer if you use Asset Inventory. To make changes, you need roles/storage.admin on the buckets you will fix or an equivalent custom role. Do not start with admin if a read-only pass can answer the question.
Install and authenticate the gcloud CLI. Run gcloud auth login and gcloud config set project for the first project, then switch as needed. If you work across many projects, use gcloud projects list or Asset Inventory rather than clicking through the console. Keep a spreadsheet or ticket with bucket name, project, owner, public binding, and intended use. You will need it for cleanup.
Agree on a change window before you remove anything. Public buckets often serve a website, a mobile app config, or a partner feed. The bucket owner should confirm the dependency. Take a copy of the current IAM policy and ACLs before edits so you can compare later. Decide how you will treat signed URLs. A signed URL grants temporary access without a public IAM binding. It is not the same as a public bucket, but a URL with a long expiry and broad access can act like one.
How to do it
Start with an inventory, not with a single project. The project left out of the review is often one created for a test, a pilot, or a vendor. Asset Inventory gives you one list across the organization. If you do not have it, loop through projects with gcloud storage buckets list.
Then split the checks. Bucket IAM tells you who can access the bucket and its objects. Object ACLs matter only on buckets that use fine-grained access. Public access prevention is a separate setting that can block public bindings even when someone tries to add them. Check all three, because a clean IAM policy does not prove the objects are private.
Treat public buckets as exceptions that need a name and a reason. A bucket that serves your marketing site images is public on purpose. A bucket that holds invoices is not. Write the reason in the ticket and put the owner's name next to it. For everything else, remove the public binding, enable uniform bucket-level access, and set public access prevention to enforced.
Run the unauthenticated test last. It confirms what an outsider sees. Use curl with the -I flag so you fetch headers, not the file body. If you get 200, the object is public. If you get 403, the request was denied. Test a few known object paths, not just the bucket root.
- Inventory every bucket. Use gcloud asset search-all-resources with the storage.googleapis.com/Bucket asset type at organization scope, or run gcloud storage buckets list for each project. Include projects in every folder and every region. Save the output with project IDs and bucket names.
- Check bucket IAM for public members. Run gcloud storage buckets get-iam-policy gs://BUCKET and search for allUsers and allAuthenticatedUsers. Either one is a public path. allAuthenticatedUsers means any Google account, not just people in your company.
- Check object ACLs on fine-grained buckets. Run gcloud storage buckets describe gs://BUCKET and look at uniformBucketLevelAccess. If it is false, object ACLs can grant public read even when the bucket IAM is clean. Inspect a sample of objects with gcloud storage objects describe or plan to switch the bucket to uniform access.
- Check public access prevention. In the same describe output, read iamConfiguration.publicAccessPrevention. A value of enforced blocks public bindings. A value of inherited means the bucket follows the project or organization policy. Also check the org policy constraint constraints/storage.publicAccessPrevention.
- Review website and app configs. List buckets with a website configuration and buckets used by Cloud Storage for Firebase, load balancers, or mobile app config files. Mark each one as intended public or not. Get the owner to confirm before you remove access.
- Confirm with an unauthenticated request. Run curl -I against the storage.googleapis.com host for the bucket and object path from a machine with no Google Cloud credentials. A 200 response means the object is readable by anyone. A 403 means it is blocked. Do not download sensitive files to prove the point.
- Remove public bindings and add guardrails. Remove allUsers and allAuthenticatedUsers from the bucket IAM policy. Enable uniform bucket-level access where the app allows it, then set public access prevention to enforced. Recheck the bucket and the app before you move to the next one.
How to check it worked
Re-run the IAM check on every bucket you touched. The get-iam-policy output should have no allUsers or allAuthenticatedUsers. If a bucket still shows one, check whether a project or organization policy is adding it, or whether another admin changed it back.
Run the unauthenticated curl test again. Expect 403 for private objects and 200 only on the public exceptions you wrote down. Test from outside your corporate network if you can, because a proxy or VPN can hide the real result. Check a known object path and a path that should not exist.
Look at Security Command Center or your asset inventory the next day. New public bucket findings should stop appearing for the buckets you fixed. In Cloud Audit Logs, filter for SetIamPolicy on storage.googleapis.com and confirm the changes came from your team. Close the ticket only after the owner confirms the app still works.
Mistakes to avoid
The first mistake is trusting bucket IAM alone. On a bucket with fine-grained access, an object ACL can grant public read while the bucket policy looks clean. Either check object ACLs or move the bucket to uniform bucket-level access after you confirm nothing depends on per-object permissions.
The second is treating allAuthenticatedUsers as safe because it requires a login. It means any Google account on the internet. That is public for practical purposes. Remove it with the same urgency as allUsers.
The third is turning on public access prevention everywhere at once. A static website bucket or a public dataset will break, and the owner will not know why. Keep a short exception list with names and review dates. Set the guardrail at the organization level after the exceptions are documented.
The fourth is calling a signed URL a fix. A signed URL with a short expiry is a reasonable way to share one file. A signed URL with a long expiry and no revocation plan is a public link with extra steps. Track who can sign URLs and cap the lifetime. If you want an outside view, the Cloud security review from Cloudverg at $6,500 fixed price is one option: a read-only review of up to five cloud accounts or subscriptions, with public exposure checks and a ranked fix list. Other options include an internal review or a specialist consultant.
Frequently asked questions
Does a private bucket IAM policy mean my objects are private?
Not always. On a bucket with fine-grained access, an object ACL can still grant public read. Check uniformBucketLevelAccess and public access prevention, or switch the bucket to uniform access after confirming the app does not need per-object permissions.
What is public access prevention in Google Cloud Storage?
It is a bucket setting that blocks public IAM bindings. The value enforced blocks them; inherited follows the organization policy. You can also enforce it across projects with the org policy constraint constraints/storage.publicAccessPrevention.
Can I make a bucket public for a static website and still keep other data private?
Yes, but use a separate bucket for public assets and never mix them with private data. Keep public access prevention off for that bucket, document the exception, and set the guardrail on every other bucket.