Your Scanner Checks Settings. The Breach Was a Combination.
47 setting-level findings contain less signal than 3 combination-level findings. The difference is the unit of analysis — and changing it changes everything
Run a cloud security scanner against a production AWS account. You’ll get back something like 47 findings. Maybe 200. Maybe 2,000. Each one says: this resource has this property set to this value, and that value is wrong.
MFA not enabled on this IAM user. Encryption at rest not configured on this RDS instance. Public access block not set on this S3 bucket. Logging not enabled on this CloudTrail trail. Default VPC exists in this region.
Each finding is correct. Each finding is a property of one resource. Each finding is a setting.
Now look at the Capital One breach. The attacker exploited a path through four resources: a public-facing WAF, an EC2 instance with an IAM role, the role’s policy permitting S3 read access, and an S3 bucket containing 100 million customer records. None of these four resources would have appeared as a finding in a typical scan. The WAF was supposed to be public. The EC2 instance was supposed to have a role. The role was supposed to have S3 permissions. The bucket was supposed to exist.
No individual setting was wrong. The combination was exploitable.
The scanner found 47 settings. It missed the one combination that mattered. And no amount of tuning, filtering, or prioritizing the 47 findings would have surfaced the combination — because the combination isn’t a setting. It’s a relationship between settings across multiple resources that the scanner’s architecture cannot express.
The unit of analysis determines what you can find
This isn’t a capability gap that gets fixed with a better rule library. It’s a structural limitation determined by what the tool uses as its unit of analysis.
A scanner’s unit of analysis is the individual resource. The inner loop is: for each resource, for each rule, does the resource’s configuration match the rule? One resource, one rule, one verdict. Every finding is a fact about one resource in isolation.
The breach’s unit of analysis is the resource composition. The exploitable condition isn’t a property of any single resource — it’s a property of the tuple: (WAF, EC2, IAM role, S3 bucket). The condition emerges from the relationships between them: the WAF routes to the EC2, the EC2 has the role, the role has the policy, the policy permits the action, the action reaches the bucket.
A tool whose inner loop is resource × rule literally cannot express tuple × property. It’s not that the rule is missing from the library. It’s that the architecture doesn’t have a slot for rules that range over multiple resources simultaneously.
This is the same distinction the previous post identified between Lamport’s safety invariant (a property of the system state) and a pattern match (a property of one component). The unit of analysis is where that distinction becomes operational.
What 47 findings actually tell you
Here’s a typical scan output, abbreviated:
FINDING: IAM user 'deploy-bot' has no MFA
FINDING: IAM user 'deploy-bot' has console access enabled
FINDING: IAM user 'monitoring-svc' has access key older than 90 days
FINDING: S3 bucket 'logs-archive' has no versioning
FINDING: S3 bucket 'logs-archive' has no lifecycle policy
FINDING: RDS instance 'analytics-db' has no encryption at rest
FINDING: RDS instance 'analytics-db' allows major version auto-upgrade
FINDING: Default VPC exists in us-west-2
FINDING: Default VPC exists in eu-west-1
FINDING: Default VPC exists in ap-southeast-1
... (37 more)
Each finding is true. Each is a deviation from a best-practice baseline. And each is an individual setting on an individual resource.
A security engineer reading this list faces two problems. First, the signal-to-noise ratio. Are all 47 findings equally important? Obviously not — but the scanner can’t tell you which ones matter more, because the severity is assigned per rule, not per context. “No MFA on an IAM user” is medium severity regardless of whether that user has administrator access or read-only access to one non-production bucket.
Second, and more fundamentally: the list is complete but the coverage is not. Every setting deviation has been flagged. But the exploitable condition — the one that would actually lead to a breach — might not involve any setting deviation at all. The Capital One path used correctly-configured resources in a dangerous composition.
47 findings. Zero of them pointed at the actual risk.
What 3 combination findings tell you
Now consider what changes when the unit of analysis shifts to the composition:
COMPOUND FINDING: Principal chain — EC2 instance 'web-proxy-1' has
IAM role 'web-proxy-role' → role policy permits 's3:GetObject' on
bucket 'customer-data-prod' → bucket contains resources tagged
'DataClassification: PII' → instance is in public subnet with
internet gateway route.
SEVERITY: Critical
CORPUS: Capital One incident pattern, MITRE T1078.004
COMPOUND FINDING: Privilege escalation — IAM user 'deploy-bot' has
'iam:PassRole' permission scoped to '*' → can pass role
'admin-emergency' which has 'AdministratorAccess' → effective
privilege exceeds user's own permission boundary.
SEVERITY: Critical
CORPUS: Rhino Security privilege escalation taxonomy
COMPOUND FINDING: Ghost reference — Security group 'sg-0a1b2c3d'
references VPC endpoint 'vpce-deleted-12345' which no longer exists
→ traffic rules referencing deleted endpoint have undefined behavior.
SEVERITY: High
CORPUS: Zellar defect classification
Three findings. Each one describes a relationship between multiple resources. Each one identifies a condition that no single-resource scan would surface. Each one points at an exploitable path or an integrity violation, not a setting deviation.
The first finding says: there’s a path from the public internet to PII-classified data through your IAM graph. The second says: a user can escalate to administrator access through a role-passing chain. The third says: a security group references something that was deleted, and the behavior is now undefined.
These three findings contain more actionable signal than the 47 setting-level findings combined. Not because the settings don’t matter — MFA should be enabled, encryption should be configured — but because the settings are individually non-critical while the combinations are individually exploitable.
Why the inner loop matters architecturally
The difference between resource × rule and tuple × property isn’t just what findings you get. It determines the entire architecture of the tool.
A scanner with resource × rule can be stateless. It processes one resource at a time, applies rules, emits findings, moves to the next resource. It doesn’t need to hold the full resource graph in memory. It doesn’t need to know about relationships between resources. Each rule is independent. The architecture is simple, fast, and parallelizable.
A tool with tuple × property must be stateful. It needs the full resource graph — every resource, every relationship, every permission chain — loaded and queryable. It needs to evaluate properties that range over multiple resources simultaneously, which means graph traversal, transitive closure over role-assumption chains, and composition of policies across boundaries.
This is why you can’t fix a scanner by adding better rules. The architecture doesn’t support rules that reference multiple resources. Adding a rule that says “alert when an EC2 instance in a public subnet has a role with S3 permissions on a PII-classified bucket” requires the rule engine to hold the EC2 instance, the subnet, the route table, the IAM role, the IAM policy, the S3 bucket, and the bucket’s tags simultaneously. A resource × rule engine processes one of these at a time. It doesn’t have a “simultaneously.”
The fix isn’t a better rule. It’s a different inner loop.
The three kinds of findings that only exist at the combination level
Once the unit of analysis shifts to compositions, three classes of findings become visible that are structurally invisible to setting-level scans.
Principal chains. A path from a reachable principal (public-facing compute, federated identity, cross-account trust) through the permission graph (role assumptions, policy attachments, resource policies) to a sensitive resource (tagged PII, tagged production, framework-mapped HIPAA). The path is the finding. No individual resource in the path is misconfigured — the composition is exploitable.
Privilege escalation. A user or role with a permission that, when composed with another resource’s configuration, produces effective privileges exceeding the original principal’s boundary. iam:PassRole to a higher-privilege role. iam:PutRolePolicy on a role attached to the principal itself. These aren’t misconfigurations — they’re emergent properties of how IAM policies compose. They’re invisible to a scan that evaluates each policy in isolation.
Ghost references. Resource A references resource B, but resource B was deleted. The reference dangles. A security group rule pointing at a deleted VPC endpoint. An EventBridge rule targeting a deleted Lambda function. A Cognito identity pool linked to a deleted IAM role. The finding exists only in the relationship between two resources — one that exists and one that doesn’t. A setting-level scan sees resource A and its properties. It doesn’t know that resource B should exist but doesn’t, because “should exist” is a relationship property, not a setting property.
Each of these classes is common in production environments. Each is exploitable or represents an integrity violation. And each is structurally undetectable by a tool whose unit of analysis is the individual resource.
The quantitative gap
How big is the gap? Across 10 AWS service domains — IAM, VPC, KMS, S3, Lambda, ECS, EKS, RDS, Step Functions, OpenSearch, EventBridge — an audit found 180 distinct compound risk patterns. Each pattern describes a multi-resource composition that represents an exploitable condition or a safety violation.
The standard compliance frameworks — CIS AWS Foundations Benchmark, PCI DSS, HIPAA, SOC 2, NIST 800-53 — map controls to individual resource properties. No framework has a control for “does any privilege escalation path exist through the IAM graph.” No framework has a control for “does a principal chain connect a public-facing identity to a PII-classified resource.” No framework has a control for ghost references.
The gap isn’t in the tool’s implementation. It’s in the framework’s unit of analysis. The frameworks were designed when the unit of analysis was the individual resource. The breach surface moved to compositions. The frameworks didn’t follow.
180 compound patterns across 10 services. Zero coverage in any compliance framework. That’s the quantitative measure of the unit-of-analysis gap.
The shift
The progression through this series has been building toward one architectural change:
Post 1 identified the reinforcing loop: more tools, more noise, more breaches. The tools share the same architecture. Adding more of the same architecture doesn’t break the loop.
Post 2 identified the mathematical ceiling: the state space is too large for pattern matching to cover. Coverage is structurally bounded by the number of patterns in the library.
Post 3 identified the incident pattern: five outages, three vendors, same root cause — implicit invariants that nobody wrote down.
Post 4 identified the theoretical fix: Lamport’s safety invariant, expressed as a predicate, verified against the actual system state.
This post identifies where the fix must operate: at the composition level, not the setting level. The safety invariant must range over tuples of resources, not individual resources. The inner loop must be tuple × property, not resource × rule.
The setting-level scan isn’t wrong. It’s necessary — MFA should be enabled, encryption should be configured, logging should be on. But it’s insufficient. The breach surface is at the composition level. The tool that only checks settings is checking the floor while the risk is in the ceiling.
What comes next
The next post: “The Tool That Checks Compositions — and How It Works.” We go from the problem to the mechanism. What does a tuple × property inner loop actually look like? How do you evaluate a safety invariant over a resource graph? What happens when you ask “does any path exist?” instead of “does this specific path exist?” — and the engine finds paths nobody cataloged.
This is where the series transitions from theory to architecture. The invariant is the right primitive. The composition is the right unit of analysis. The next question is: what does the tool that implements both actually do?
If you’ve been waiting for the concrete part, it starts next.
This is the fifth post in a series on the structural foundations of cloud security. Post 1 covered the reinforcing loop. Post 2 covered the mathematical ceiling. Post 3 covered the incident pattern. Post 4 covered the theoretical foundation (Lamport’s safety invariant). This post covers the unit-of-analysis shift. Next: the mechanism.The Missing Primitive in Cloud Security Was Discovered in 1977

