SOC 2 controls that fail most often, and how to set them up on AWS

Table: SOC 2 controls and the AWS setting behind each

Some finding catches almost everyone in their first SOC 2 report: "the quarterly access review didn't happen in the second quarter."

No breach, no broken server ...

Just a calendar item everyone thought somebody else owned.

That is what most audit failures look like. So this post covers which controls fail most often, and how to set up AWS so they either run themselves or leave their own paper trail.

First, two bits of audit vocabulary. A "control exception" is the auditor finding one case where the thing you promised to do didn't happen. A "qualified opinion" is worse. It means the auditor could not sign off cleanly, and your buyer's security team will read that line first.

The audit firm CBIZ read 193 SOC reports for its 2024 SOC Benchmark Study. It found that 54.9% of reports had at least one control exception and 10.9% carried a qualified opinion. So about half of all reports pick up a finding. Roughly one in ten gets the serious version.

And what caused them? Mostly routine work that didn't happen on time. The top four were approvals and reviews, user access reviews, leavers whose access was never removed, and change management. Failed access reviews had become the single leading reason for a qualified opinion.

KPMG's UK practice saw the same thing across more than 400 reports. Look at where access controls land in its SOC Reporting Benchmarking 2024. They were a small slice of what got tested, and a much bigger slice of what failed:

Column chart: system access controls were 8% of all controls but 17% of all exceptions. Source: KPMG UK, SOC Reporting Benchmarking 2024.
The same study found that 89% of exceptions on operating effectiveness tests were in manually operated controls. The fix it suggests is to automate access revocation and access reviews.

That gap is good news, in a way. If the controls that fail are mostly steps a human forgot, then setup can do much of the work for you. Here are the ones I would look at first.

Why leavers keep their access

Someone resigns. HR disables their email on the Friday. Three months later the auditor asks for proof their AWS access went away too, and nobody can find any.

The auditor will pick a sample of people who left during the audit period. For each one, you must show their access went away inside the window your own policy promises. It usually fails for one reason. AWS access was never wired up to the system that knows who still works there.

That system is your identity provider: the one place that holds everyone's login. Google Workspace, Microsoft Entra ID and Okta are the common ones. Trouble starts when AWS access sits outside it, as separate "IAM users". An IAM user is a login made directly in AWS, with its own password and keys, that only AWS knows about.

These are still everywhere. Datadog's State of Cloud Security 2025 found that 39% of firms still use IAM users in some form. About one in five rely on them alone. That last group has no central off switch at all.

The fix is to make your identity provider the only door. Move people to IAM Identity Center and connect it with automatic SCIM provisioning. SCIM is just a standard way for that system to tell AWS "this person joined, this person left". Think of a hotel cancelling your key card at checkout, rather than hoping each door forgets you.

Now switching someone off in Okta takes their AWS access with it. Better still, the identity provider's own log becomes your evidence. No screenshots to remember. Once everyone has moved across, delete the old IAM users.

Access reviews nobody did, or did alone

Almost every policy promises a quarterly review of who can reach production. CBIZ found two ways that promise breaks. Sometimes the review was simply skipped. Sometimes it happened, but the person approving the access list was also on it.

Neither of those is a technical problem. But your setup decides whether the review takes twenty minutes or a grim afternoon. Guess which version gets skipped.

Identity Center helps because access becomes a short list. Instead of hundreds of separate policies, you get a handful of permission sets handed to groups. A permission set is a named bundle of rights, like "read-only on production", that you give to a group rather than a person.

Then the review itself is a small routine:

One warning from the auditors cited above. Auditors now test whether the list you reviewed was complete. So keep the raw command output and the date you ran it. A tidied-up spreadsheet proves nothing about what you left out.

Access keys that outlive the person who made them

Somebody set up a build server three years ago and gave it an access key. An access key is a username and password for software, and it does not expire. The server still runs. The key has never changed. The person who made it has left.

SOC 2 expects credentials to be managed, and that key is hard to defend. It is also very common:

Donut chart: 59% of AWS IAM users have an active access key older than one year; 41% have a newer key or no active key. Source: Datadog, State of Cloud Security 2025.
Datadog adds that over half of those old keys had not been used in more than 90 days, which makes most of them easy to delete.

The better pattern is to stop storing keys at all. For your build pipeline, set up an OIDC identity provider in IAM. OIDC lets AWS trust a short-lived proof of identity from another system, so nothing permanent sits in your settings. Think of a visitor badge that works for one afternoon, not a copied office key.

GitHub Actions supports this directly and gets fresh credentials for each job. Anything running inside AWS should use a role instead. A role grants rights to the machine, so there is no secret to store.

Then make it hard to backslide. Turn on the AWS Config rules access-keys-rotated and iam-user-unused-credentials-check. A new long-lived key then shows up at once as non-compliant. IAM Access Analyzer will also flag access nobody is using.

Changes nobody approved

The auditor picks a few changes that reached production and asks one simple question. Who approved this? Two habits make it impossible to answer. One is pushing commits straight to the main branch. The other is fixing things by hand in the AWS console.

For code, a repository ruleset on the default branch solves it. Require a pull request, one approval from somebody other than the author, and passing checks. Your pull request history then is the evidence, which is the whole point.

For infrastructure, deploy through that same pipeline with Terraform or CloudFormation. Use permission sets to limit who can change production by hand. Console edits should be rare, not routine. When one does happen, CloudTrail records who did it, and your change policy should say how it gets approved after the fact.

Logging that only covers the regions you remembered

A common surprise: the trail was set up years ago in one region, and the team has since launched services in three more. Everything there is invisible.

What you need is a record of admin activity across the whole estate that nobody can edit without leaving a trace. Create an organization trail from your management account. It covers every account and every region for you, including accounts somebody sets up next year.

Then guard it from tampering. Turn on log file integrity validation, which lets you prove the logs were not changed. Write them to a bucket in a separate logging account with S3 Object Lock. Object Lock makes a file impossible to delete or overwrite for a set time. If you expect to answer questions from those logs, CloudTrail Lake lets you query them with SQL.

Do this one first. Every other control here starts leaving evidence the day you turn it on, and logging is what proves it did.

Open buckets and unencrypted disks

Turn on S3 Block Public Access at the account level. Datadog suggests locking that setting behind an SCP. An SCP is a rule for the whole org that not even an account admin can switch off. Then turn on EBS encryption by default in each region you use, so new disks are encrypted with nobody ticking a box.

To keep an eye on things after that, switch on Security Hub with the AWS Foundational Security Best Practices standard. Add GuardDuty to watch for odd activity.

Fair warning. That first list of findings will be long and a bit crushing. Start with production accounts that hold customer data. For the rest, write down why you accept the risk. At audit time a documented exception is a fine answer, where an unexplained one is not.

Backups nobody has ever restored

Your auditor will not ask whether backups run. They will ask when you last restored one. That is a much harder question.

AWS Backup restore testing runs restores on a schedule and records whether each one worked. Backup Audit Manager turns the results into reports. Together they turn a chore everybody avoids into a record that writes itself.

The evidence each control leaves

ControlAWS or tooling settingEvidence the auditor samples
Timely removal of leaversIdentity Center with SCIM from the identity providerOffboarding ticket and identity provider log
Periodic access reviewPermission sets assigned to groupsCLI export, reviewer sign-off by someone else
Credential managementOIDC for CI, roles for workloads, Config rulesConfig compliance history, credential report
Change approvalRepository ruleset, deploys through the pipelinePull request approvals, CloudTrail for console changes
Audit loggingOrganization trail, integrity validation, Object LockTrail configuration, validation output
Data exposure and encryptionBlock Public Access with SCP, EBS default encryption, Security HubSecurity Hub findings and documented exceptions
Backup and restoreAWS Backup plans and restore testingRestore test results, Backup Audit Manager reports

None of this replaces the written policies, training records and vendor reviews SOC 2 also wants. It does cover the controls that most often go wrong.

What you can do this week

Download the IAM credential report and count how many users and keys are older than a year. Check that an organization trail exists and really does cover every region. Turn on Block Public Access at the account level.

Then book the first quarterly access review, with an owner and a second reviewer who is not on the list. That last one is the cheapest job here. By CBIZ's numbers, it is also the most costly to skip.

Cloudacio builds, secures and runs platforms on AWS, GCP and Azure. More about us.

← All articles