Defenders Think in Lists. Attackers Think in Graphs. Your Tools Should Too.
✓ Human-authored analysis; AI used for formatting and proofreading. Cloud security has a structural problem that scanners cannot fix. Most tools catch individual misconfigurations well enough. The problem is that indiv
✓ Human-authored analysis; AI used for formatting and proofreading.
Cloud security has a structural problem that scanners cannot fix. Most tools catch individual misconfigurations well enough. The problem is that individually correct configurations compose into dangerous states that no individual check can detect. Every setting passes. The system is unsafe. Breaches live in the gap between those two facts.
This article explains why the gap exists, why it's not fixable by improving scanners, and what it takes to close it.
The checklist trap
Most cloud security tools work the same way. They iterate through resources, check each one against a baseline, and report pass or fail.
- Is encryption enabled?
- Is the bucket private?
- Is MFA enforced?
Each check evaluates one attribute on one resource in isolation.
This works for a specific class of problems where the settings are universally wrong regardless of context. A bucket with Principal: * in its policy is public. That's a fact. The check is correct. The finding is actionable.
But cloud infrastructure isn't a list of independent resources. It's a directed graph. Resources are nodes. Permissions, trust relationships, network routes, and data flows are edges. The security question isn't whether each node has the right attributes, it's whether the edges between nodes create paths that shouldn't exist.
A checklist tool evaluates nodes. An attacker navigates edges.
The compound risk problem
Consider three AWS resources, each correctly configured:
A Cognito identity pool allows unauthenticated access. This is intentional. The mobile app needs to let users browse a product catalog before signing in. A scanner checks: "Is unauthenticated access configured?" Yes. That's the design.
An IAM role grants s3:GetObject on a specific bucket. The role is scoped to one bucket, uses least-privilege actions, and has a trust policy that only allows Cognito to assume it. A scanner checks: "Does this role have admin access?" No. "Is the trust policy scoped?" Yes.
An S3 bucket is tagged data_classification: phi and contains patient health records. The bucket is private, encrypted with KMS, and has public access blocked at the account level. A scanner checks: "Is the bucket public?" No. "Is encryption enabled?" Yes. "Is public access blocked?" Yes.
Three resources and scans. Zero findings.
But the identity pool maps unauthenticated users to the IAM role. The IAM role can read the PHI bucket. An anonymous internet user can reach patient health records by requesting temporary credentials from the identity pool, assuming the mapped role, and calling s3:GetObject. No authentication required.
The vulnerability is in the composition. Three edges connecting three nodes into a path that the operator didn't intend. Scanners that evaluates nodes in isolation will not find it because it doesn't exist on any single node.
This is a design constraint of the checklist model. The model evaluates attributes. The risk is in relationships.
Why more checks don't help
Adding more checks such as identity pool's role mapping, which buckets the mapped role can access, whether those buckets contain sensitive data don't help. Each additional check is still evaluating one attribute on one resource. The question "can an anonymous user reach PHI through this chain of three services?" requires evaluating the composition of all three simultaneously. Something a checklist model cannot express.
Adding more checks to a checklist tool is like adding more words to a spell checker. The spell checker gets better at verifying individual words. It never learns to verify that the sentences make sense together. The problem is at a different level of abstraction.
AWS cannot patch this. They provide the graph primitives for IAM, SCPs, resource policies, VPC endpoints, trust relationships. The interactions between those primitives are defined by each organization's specific architecture. AWS can't evaluate compositions that is outside of the shared responsibility model.
Reactive tools in a proactive problem
Checklist model is reactive. Scanners look for symptoms such as settings that match known-bad patterns. A bucket with Principal: * matches a pattern. An IAM role with Action: * matches a pattern. The tool recognizes the symptom and reports it.
But the symptom isn't the problem. The problem is why the configuration exists. A role with broad permissions was created for a reason such as a migration, a vendor integration, a debugging session. The permissions were never scoped down afterward. Few months later, a scanner flags the broad role. The security team files a ticket. The engineering team says "we might need those permissions." The ticket sits. The role stays. Next scan, same finding. Next ticket, same conversation.
This is the alert fatigue cycle. The tool reports the same symptoms every scan. The findings are technically correct. The role is overpermissioned. But the finding doesn't explain why the configuration is dangerous in context, what specific risk it creates in composition with other resources, or the operator's original intent. Without that context, the finding is noise.
A proactive tool asks a different question. Instead of "does this setting match a known-bad pattern?" it asks "does this configuration contradict what the operator declared?" The answer: the operator said the role is readonly (via a role-type tag), but the permissions include secretsmanager:GetSecretValue and lambda:InvokeFunction. The finding isn't "broad permissions detected". It's "your permissions contradict your stated purpose, and here are the specific actions that don't belong."
The first finding generates a ticket that sits. The second finding generates a conversation that resolves: either the tag is wrong (update it to match what the role does) or the permissions are wrong (remove the actions that don't match the declared purpose). Both outcomes are concrete. Neither requires a follow-up scan to verify, re-run the analysis, confirm the contradiction is gone.
This is the difference between surfacing symptoms and surfacing root causes. A symptom is "this role has 47 actions." A root cause is "this role was declared as readonly but has accumulated secrets_access and compute_control permissions over 18 months, and 8 of 12 accessible services have never been used by any principal assuming it." The first is a number. The second is a narrative with a specific remediation: remove the 8 unused services, split the incompatible categories, and update the tag to match the role's actual function.
Scanners generate hundreds of findings per scan because they report every symptom on every resource on every run. Most are true positives, the settings really are suboptimal. Few are actionable where the team can't tell which findings represent real risk in composition and which are baseline noise that has existed since the environment was created. The signal-to-noise ratio degrades as the environment grows, because the number of individually-suboptimal settings grows linearly while the number of dangerous compositions grows much slower.
Intent verification inverts the ratio. Findings fire only when the configuration contradicts a declared intent or when multiple controls compose into a compound chain that crosses a severity threshold. A single overdue vendor review date (1 of 5 delegation controls) doesn't fire the compound chain. It's a governance reminder. Three concurrent delegation failures (unknown principal + scope exceeded + vendor can make bucket public) fires the chain at CRITICAL. That's a real problem with a specific remediation for each axis.
The result is fewer findings that each carry more information. Each finding names the specific properties, contradiction, and the remediation. Each compound chain names the composition pattern, the member controls that contributed, and the threshold that was exceeded. The security team reads 5 findings instead of 500, and each one tells them what to do.
The missing variable: intent
Even if a tool could evaluate compositions, it would need to know which compositions are dangerous. Dangerous depends on what the operator intended.
A public S3 bucket hosting CSS files for a static website is correct. The same configuration on a bucket containing patient records is a HIPAA violation. The bucket settings are identical. The verdict depends on a variable the tool doesn't have: what the operator declared about the data inside.
In formal terms: verdict = f(configuration, intent). The configuration is visible. It's the IAM policy, the bucket settings, the identity pool configuration. Intent is supplied by the operator, and its value changes for every deployment.
This variable cannot be baked into a model during training. It is unknown before training. No dataset contains this operator's future decisions. It is unknown during training. Patterns from other operators' configurations don't carry this operator's intent. And it is unknown after training. The value is created when the operator makes a deployment decision, after the model is deployed, by an operator the model has never observed.
A scanner that learns "public buckets are bad" produces false positives on every static website. A scanner that learns "public buckets are fine" misses every PHI exposure. The pattern doesn't generalize because the verdict isn't in the configuration. It's in the relationship between the configuration and the operator's declared intent.
Tags are useful in declaring intent. data_classification: phi is the operator's machine-readable declaration of intent: "this bucket contains patient records." Without it, no tool, scanner, verifier, or human auditor can determine whether the configuration is correct. An operator expresses the correctness for a specific deployment by tagging resources.
Cloud security as a graph problem
If you visualize an AWS environment, you see a directed graph:
Nodes are principals (users, roles, agents), resources (buckets, databases, endpoints), and network boundaries (VPCs, subnets, peering connections). Edges are permissions (can assume, can read, can invoke), trust relationships (trusts service X, trusts account Y), data flows (indexes bucket Z, reads from source W), and network routes (peers with VPC Q, routes to CIDR R).
A scanner evaluates node attributes: is this bucket encrypted? Is this role scoped? These are necessary checks. They verify that individual nodes meet a baseline.
But the security question is about paths: can an anonymous user reach patient data? Can a compromised vendor account assume a production role? Can a stale EC2 instance pivot into the production VPC through a forgotten peering connection?
Path questions require evaluating the graph. And the graph grows combinatorially. Each new resource adds nodes, each new policy adds edges, and the number of possible paths increases faster than any team can review manually.
State space collapse
The combinatorial explosion occurs in an environment with hundreds of resources and thousands of policies. The state space has billions of possible configuration states. Mapping every possible path is done by graph visualization tools like BloodHound and Cartography. They're valuable for exploration. They're also noisy, showing 10,000 possible paths when the security team needs to know which 3 matter.
The alternative to mapping every path is defining which states are forbidden and checking whether any of them are reachable. Instead of asking "where can an attacker go?" which produces an explosion of paths. You ask "does any state exist where a public-facing agent can reach PHI data without a guardrail?" The answer is binary: reachable or not. If not, an entire class of risks is eliminated regardless of how many paths exist in the graph.
Z3, developed by Microsoft Research for verifying flight control software and CPU designs, takes a set of assertions (the configuration facts) and a query (the forbidden state) and returns either sat (the state is reachable — unsafe) or unsat (the state is mathematically impossible, proven safe). The proof is deterministic. Run it again, get the same answer. Run it on a different solver (cvc5 from Stanford, Yices from SRI International), get the same answer independently verified.
The solver doesn't map the graph. It collapses the state space. Billions of possible configurations reduce to one question: is this specific forbidden state reachable from these specific facts? If not, the security team has a proof. A mathematical proof that the dangerous state cannot exist in this configuration.
This proof is valid within the scope of exported SIR facts; the Fact Export reference names which property domains the SIR currently covers.
Safety engineering, not threat hunting
Most security tools are adversary-focused: they look for threats, attacks, and exploits. The question is "is an attacker present?" or "can an attacker do X?"
But a configuration that allows unintended access is a failure regardless of whether an attacker is present. A bucket that's one setting change away from exposing patient data is waiting for a tired engineer making a change. The dangerous state is latent in the configuration. The trigger is irrelevant.
Aviation safety doesn't wait for a crash to find structural problems. Nuclear safety doesn't wait for a meltdown. They define safety envelopes, sets of invariants that the system must satisfy at all times and verify continuously that the system stays within them. If a fuel line configuration allows pressure to exceed a threshold, the system is unsafe now, before the pressure rises.
Cloud infrastructure deserves the same approach. A configuration where an anonymous user can reach PHI data is structurally unsafe. It doesn't matter whether an attacker has exploited it yet. The latent failure exists in the composition of permissions, trust relationships, and data classifications.
Intent verification
Intent verification reads the operator's declarations in tags, policies, trust relationships, role purpose tags, vendor registries and checks whether the infrastructure's composition respects those declarations.
When the operator tags a bucket data_classification: phi, they're declaring: "this contains patient records." When they configure a Bedrock knowledge base to index that bucket, they're declaring: "serve this data through the RAG pipeline." When they skip the guardrail on the agent, they're declaring: "no content filtering needed." Intent verification checks these three declarations against each other: a PHI bucket indexed by an unguarded public-facing agent is a contradiction in the operator's own stated intent.
The findings aren't based on heuristics about what might be dangerous. They're based on logical composition of explicit declarations. The operator said "this is PHI." The operator said "this agent serves public queries." The operator said "no guardrail." The tool says "those three decisions are incompatible." It's arithmetic on expressed intent.
Each finding traces back to specific properties in specific configuration files through deterministic identifiers. The trace names the observation file, the property path, and the timestamp. When the operator remediates and re-runs the analysis, the findings drop to zero and the compound chains go silent. The before-and-after delta is the remediation proof.
Evaluation criteria for tools
If you're evaluating cloud security tooling, separate the problems before evaluating the tools:
Pattern-Recognizable problems SQL injection, XSS, buffer overflows, misconfigured encryption have universal verdicts. A scanner trained on enough examples recognizes the next one. These are well-served by existing tools.
Intent-Dependent problems PHI bucket in a public RAG pipeline, vendor with irrevocable access, shadow admin role accumulated over two years have verdicts that depend on this specific operator's declared intent. These are structural composition problems that require evaluating the graph, not the nodes, against the operator's own declarations.
Scanners solve the first class. They cannot solve the second. Because the problem is at a different level of abstraction. Checking nodes doesn't find path problems. Checking attributes doesn't find composition problems. Checking universal patterns doesn't resolve intent-dependent verdicts. And reporting symptoms without root causes generates noise that teams learn to ignore.
The compound risk problem is not fixable by any patch to any scanner. The only way to address it is to move from reactive symptom detection to proactive intent verification from evaluating nodes to proving paths, from universal baselines to intent-aware invariants, from hundreds of correct but not actionable findings to a handful of findings that each name the root cause and the specific fix.
Stop chasing symptoms. Start proving safety. A configuration that contradicts its operator's declared intent is a broken system and the fix is in the contradiction.
The intent verification is implemented in Stave, an open-source static analysis tool for cloud infrastructure. Stave evaluates 3,000+ controls across 74 AWS service domains and exports configuration facts to nine independent reasoning engines including SMT solvers used to verify flight software for compound risk detection and formal safety proofs. 32 AI agent identity controls, 5 AI compound chains, shadow admin detection, vendor delegation governance, and VPC lateral movement analysis ship as catalog controls with no cloud credentials required. All analysis runs on air-gapped snapshots.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.