You point a scanner at a sensitive bucket and ask the obvious question: who can read this? It returns a list of principals, and next to several of them sits a label — "conditional access." Not yes. Not no. Conditional. That label is the most honest thing a graph-based scanner can say, and it is also the moment an access review turns into an investigation. The principals it cannot resolve are exactly the ones whose access depends on a condition, and conditions are where the gaps hide.
Why the graph stops at "conditional access"
Graph-based CSPMs ingest cloud state on a schedule. At ingest time they capture the permissions of each principal and the conditions attached to them, and they label the resulting graph edge accordingly. The trouble is that conditions resolve against values that only exist at request time: the actual MFA state of the session, the source IP, the request or object tag, the encryption context. None of those exist when the graph is built, so the graph cannot say whether the condition would pass. The safest thing it can report is "access exists, conditional on X" — and leave you to work out by hand which principals actually have access in which contexts.
For an access review or a regulator, that is not an answer. "Conditional access" is a deferral, and under time pressure it gets rounded to "probably blocked." The cases where it would not have been blocked are precisely the ones that become incidents.
Evaluate the decision, do not store it
The alternative is to compute the authorization decision at query time instead of reading it off a snapshot. In that model a condition is not an ingested fact — it is a parameter you supply. Whocan does this with an env: argument: you pin the condition value you care about, the full policy chain is re-evaluated under that assumption, and the result is a definitive list of principals rather than a label.
Ask the question the graph deferred — who can read this bucket from outside our corporate IP range — and pin the source IP as the value to test:
Access from an untrusted source IP
The source IP is supplied as a parameter; the chain is re-evaluated as if the request came from there. An empty result means the IP condition holds. Names on the list are the gap.
external-ip = "203.0.113.42"
who-can(
action: "s3:GetObject"
resource: var:critical-bucket
env: {"aws:sourceip": external-ip}
)The same mechanism answers every other condition family — what if MFA were absent, what if the request came from outside the production VPC, what if the session carried a different principal tag. The condition is just a value you set.
The tags the graph never indexed
Two of the conditions a static graph handles worst are the ones modern architectures lean on hardest: resource tags and object-level tags. A reachability graph can only search what it ingested, and it does not index per-object S3 tags — a bucket can hold billions of objects, and a tagging-API call per object does not scale. So tenant isolation and data classification built on object tags collapse to "conditional access" too. The tool reads the bucket policy correctly, sees the s3:ExistingObjectTag condition, and stops there, because it cannot know the tags on any given object.
Who can read objects of one classification
The object tag is supplied as a parameter, so no per-object scan is required. Change the value to compare classifications — the difference between the two results is your access boundary.
project-tag = "Secret"
who-can(
action: "s3:GetObject"
resource: var:sensitive-bucket
env: {"s3:existingobjecttag/project": project-tag}
)DynamoDB row-level isolation between tenants
The textbook multi-tenant pattern. An empty result is audit-grade proof that the principals of tenant A cannot read the rows belonging to tenant B — the answer a "conditional access" label can never give.
foreign-tenant = "tenant-b"
who-can(
action: "dynamodb:GetItem"
resource: var:multi-tenant-table
env: {"dynamodb:leadingkeys": foreign-tenant}
)The shift is the whole point: a query engine does not try to know your data, it knows your authorization rules. A tag value is an input to the decision, not a fact to be scanned. The same machinery that answers the MFA question answers the object-tag question, because both are values supplied at evaluation time.
The same gap, organization-wide
The conditional label has a structural cousin one level up. The AWS data perimeter — the SCPs, RCPs, and endpoint policies that keep access inside trusted identities, trusted resources, and expected networks — is enforced across every account in an organization, and whether it actually holds is the same cross-account, condition-dependent question. It is large enough to deserve its own treatment, which it gets in the companion post, "You wrote the data perimeter. Can you prove it holds?"
What "conditional access" becomes
Every condition family is a parameter, not a label: MFA presence, source IP, source VPC, principal and resource tags, S3 object tags, DynamoDB leading keys, KMS encryption context. Pin the value you care about and the answer is an exact list — empty means the control holds, a name means it does not. Save any of these as a monitor and the question is re-checked on every refresh, so the answer stays current instead of aging with the last scan.
Where a graph still wins
This is a deliberate boundary, not a claim to do everything. A graph-based platform that scans workloads, network, and data gives you things a query engine does not: agentless vulnerability scanning across the fleet, end-to-end attack-path visualization spanning network, workload, and identity, data classification, and a single multi-cloud view. Whocan is an IAM authorization specialist — it answers who can actually do this, given these exact conditions, with a precision a reachability graph structurally cannot, and it is AWS-deep today rather than broad across clouds. The two are complementary: reach for the platform when the question spans the whole stack, and for the engine when the question is an authorization decision that turns on a condition.
The takeaway
"Conditional access" is the polite form of "we cannot tell you." For anyone who has to produce a yes or a no — an access reviewer, an auditor, a regulator — that label is the difference between an answer and an investigation. The conditions are not a footnote to the question; they are the question. Supply the value, evaluate the decision, and read the list.