We have audited a lot of cloud environments - AWS, Azure, GCP, and the inevitable mix of all three. The exposed storage bucket everyone worries about does still show up, but it is almost never the finding that keeps us up at night. The serious findings are quieter, and they are remarkably consistent across organisations of every size.
Here are the five we find in almost every engagement, why they matter more than they look, and exactly what to do about each.
1. IAM roles with far more power than they use
Identity is the perimeter in the cloud. And the single most common critical finding is a role, user, or service principal that has been granted broad permissions - often * on a whole service, sometimes administrative access - because it was easier than working out the exact permissions the workload needed.
This is what turns a small problem into a catastrophe. An attacker who compromises a web server with an over-permissive role does not get a web server; they get everything that role can touch. In practice that is often the whole account.
Use your provider's access analyser to compare granted permissions against permissions actually used over the last 90 days, then right-size. Prohibit wildcard actions in policy reviews. Treat any new admin-equivalent grant as a change that needs sign-off.
2. Long-lived access keys that never expire
Static credentials - access keys and secrets that sit in config files, CI systems, and developer laptops - are the credential type most likely to leak and the one attackers most want. We routinely find keys that are years old, attached to powerful identities, and used by no one who currently works at the company.
Every long-lived key is a small permanent liability. The fix is not to rotate them more often; it is to stop needing them.
Move to short-lived, federated credentials - workload identity, OIDC from your CI provider, instance roles. Inventory every remaining static key, disable those unused for 90 days, and alert on the creation of new ones. A key that expires in an hour is worth far less to an attacker than one that never does.
3. Network paths that assume the perimeter still exists
Flat networks did not survive the move to cloud, but the assumptions behind them often did. We find databases reachable from the whole VPC, management ports open to broad ranges, and security groups that reference each other in a way that quietly permits everything.
The problem is rarely one open port - it is the chain. A compromised front-end can reach a service it should never talk to, which can reach the database, which holds the data.
Security groups allowing 0.0.0.0/0 on anything other than intentional public services, and internal services with no egress restrictions. Attackers need outbound to exfiltrate and to reach command-and-control; default-open egress removes a control you get for free.
4. Logging that exists but is never watched
Almost everyone has enabled the audit log - CloudTrail, Azure Activity Log, GCP Cloud Audit Logs. Far fewer send it anywhere useful, and fewer still have an alert that would fire when something is wrong. Logs you never read give you the cost of logging with none of the benefit.
After an incident, the first question is always "what did they do?" If the answer is a bucket of logs nobody has ever queried, the investigation is slow and expensive precisely when speed matters most.
Centralise logs into one place, retain them somewhere the account being monitored cannot delete, and write a small number of high-signal alerts: new admin grants, disabled logging, root/break-glass usage, mass data access. Ten alerts that fire on real trouble beat ten thousand that nobody reads.
5. No separation between environments and blast radii
We frequently find production and non-production sharing one account, one identity plane, and one set of credentials. A compromised developer sandbox then becomes a direct route into production. The cloud makes strong isolation cheap; not using it is a choice.
Separate environments into distinct accounts or subscriptions with their own identity boundaries. Use an organisation structure with guardrail policies at the top. The goal is that compromising one environment tells the attacker nothing useful about reaching another.
Why these persist
Here is the uncomfortable part: almost none of these cost money to fix. They persist because of ownership gaps. The platform team assumes the application team scoped their role; the application team assumes the platform team locked down the network; nobody owns the audit log. Cloud security fails in the seams between teams far more often than it fails on technology.
Cloud breaches are rarely a clever exploit. They are a chain of small, well-known misconfigurations that no single person felt responsible for.
A good audit does not just list findings - it assigns each one an owner and a fix, and it re-checks that the fix held. If your environment has never had an outside set of eyes trace these chains end to end, that is the highest-value security spend most cloud-native companies can make.