A reviewer opens an identity policy and finds aws:SourceIp with 203.0.113.0/24 inside a Condition. They decide that s3:GetObject only works from that network and approve the change. Sometimes they're right. Other times the operator name ends in IfExists, and the same condition means something else.
A popular myth covers this case: "if the key isn't in the request, AWS ignores the condition and the Allow goes through." The documentation doesn't say that. With a normal operator, a missing key is a mismatch, so the condition is false and that Allow grants nothing. The thing that lets an Allow pass without the key is the ...IfExists suffix.
By the end you should be able to read three policies and say which one fails closed and which one lets GetObject through when the request carries neither aws:SourceIp nor aws:SourceVpc.
Everything here comes from the IAM User Guide, retrieved on September 29, 2026, and three local JSON files. Nothing was run against a real AWS account, in the Policy Simulator, or through a deploy.
What the User Guide says about a missing key
The condition operators page has an "Important" callout that clears up most of the confusion:
> If the key that you specify in a policy condition is not present in the request context, the values do not match and the condition is false.
The same callout adds two details. With a negated operator such as StringNotLike, a missing key makes the condition true. And this logic applies to every operator except ...IfExists and Null. The Condition element page says it more briefly: "A context key that is not present in the request is considered a mismatch." Neither page says the condition gets "ignored."
IfExists flips the missing-key case. From the operators page:
> If the condition key is present in the context of the request, process the key as specified in the policy. If the key is not present, evaluate the condition element as true.
Null does a different job: it checks whether the key exists. "true" means the key is absent. "false" means it's present and not null.
One more piece is how evaluation works. Every request starts as an implicit deny. It needs an explicit Allow, and an explicit Deny beats any Allow. An Allow whose condition is false just doesn't apply. That isn't the same as an explicit Deny, and the difference matters later.
| Operator | Key present, matches | Key present, no match | Key missing |
|---|---|---|---|
IpAddress on aws:SourceIp | match | no match | false: the statement does not grant |
StringEquals | match | no match | false (mismatch) |
...IfExists | evaluates like the base operator | evaluates like the base operator | true |
Null with "false" | true | true | false |
Policy 1: IpAddress on aws:SourceIp
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectIfPublicSourceIp",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" }
}
}]
}
The operator is IpAddress, which the documentation uses to compare a CIDR range against aws:SourceIp. The policy uses the 2012-10-17 language version, which is the current one. All three policies in this post use it.
If the request's aws:SourceIp is inside the range, the Allow grants. If it's outside, it doesn't. If the key is missing, the value doesn't match, the condition is false, and this statement grants nothing.
There's a concrete case where the key goes missing, and the global condition keys page describes it:
> The aws:SourceIp key is always present in the request context, except when the requester uses a VPC endpoint to make the request. In this case, the condition returns false and the request is implicitly denied by this statement.
So a VPC endpoint silently drops SourceIp from the context, and the policy still looks like a padlock. The same page says aws:SourceIp only works for public IP ranges. A private CIDR here won't restrict anything the way the author intended.
Policy 2: the IfExists pair from the AWS docs
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectIfExistsIpOrVpc",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddressIfExists": { "aws:SourceIp": ["203.0.113.0/24"] },
"StringEqualsIfExists": { "aws:SourceVpc": ["vpc-1234567890abcdef0"] }
}
}]
}
This pair comes from the "IP range or VPC" example on the global condition keys page, and that page also explains the result:
> If either or both keys are not included in the request context, the condition still returns true. The values are only checked if the specified key is included in the request context.
Combine this with the two availability rules and the design makes sense. A request over the internet carries SourceIp, which is checked against the range. It doesn't carry SourceVpc, since that key only exists when the request uses a VPC endpoint, so that half evaluates true. A request through a VPC endpoint is the reverse: SourceIp is missing and evaluates true, while SourceVpc is present and gets checked. Each path is checked against the key it actually sends.
The Allow that slips through is the third case, where neither key arrives. Both elements evaluate true, the whole condition is true, and GetObject is granted without any network being checked. That's exactly how IfExists is documented to behave. Nobody noticed because the suffix sits in the operator name, not in the value the reviewer was reading.
One variant deserves a look. If only the first line were there, with IpAddressIfExists by itself, any request through any VPC endpoint would arrive without SourceIp and pass. That's the very path Policy 1 kept closed.
Policy 3: failing closed on purpose with Null
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowGetObjectOnlyWhenSourceIpPresentAndMatches",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*",
"Condition": {
"IpAddress": { "aws:SourceIp": "203.0.113.0/24" },
"Null": { "aws:SourceIp": "false" }
}
}]
}
This JSON isn't copied from an AWS example. It combines two documented operators. The Null documentation uses a different key in its example, not SourceIp. Both operators are in the same Condition block, so both must evaluate true.
According to the table, Policy 1 already failed closed when the key was missing. Today, Null with "false" doesn't change that result. What it does is state the requirement that the key exist, and the evaluator enforces it. If a future PR adds IfExists to the IpAddress, the Null still blocks the request that has no SourceIp. It guards against a suffix pasted on without much thought.
When IfExists is just noise
Some keys are always in the context. The global condition keys page says aws:CurrentTime, aws:EpochTime and aws:ViaAWSService are "always included," and that aws:PrincipalAccount is in every request, anonymous ones included. For these keys IfExists never does anything, because the missing-key case never happens. All it does in practice is leave the next reviewer wondering why the suffix is there. aws:SourceIp isn't on that list: the sentence that calls it "always present" also carries the VPC endpoint exception.
Pitfalls worth knowing
StringEqualswith a CIDR. The documentation usesIpAddressfor CIDR ranges.StringEqualsis a string operator, suited to keys likeaws:SourceVpcoraws:SourceVpce. Don't expect it to understand a netmask.- Another Allow with no Condition. A false condition turns off only its own statement. If the same identity policy has another Allow for
s3:GetObjecton the same resource with no condition, the action is still granted. If you check the condition and skip the other statements, you've reviewed half the policy. IfExistsin a Deny with a negated operator. This is the opposite of the Allow case. The operators page says that with"Effect": "Deny"and something likeStringNotEqualsIfExists, "the request is still denied even if the condition key is not present". The fail-open covered here only affects Allow statements.- Service-to-service calls. When an AWS service calls another service on the principal's behalf, the target service sees the calling service's IP, and some network context is removed. The source VPC and VPCE aren't preserved either. The documentation says not to use
aws:SourceIpfor these calls and to useaws:ViaAWSServiceoraws:CalledViainstead.
When DevDojo would use each form
If the goal is "only from this network," DevDojo would use IpAddress on aws:SourceIp, or StringEquals on aws:SourceVpc/aws:SourceVpce, with no IfExists. When the key's presence is a requirement that should be visible, it would add Null with "false", as in Policy 3.
IfExists on SourceIp or SourceVpc belongs where some legitimate clients sometimes don't send the key and the team has consciously chosen to leave that path open. That decision should be written down in the PR, not left as a suffix on an operator name.
The recommendation carries less weight in two situations. The first is when the question is what a real account grants right now, with SCPs, permissions boundaries and other policies in the same evaluation. The second is when the policy is a resource policy, which this post didn't evaluate. In both cases, this reading doesn't replace an evaluation in the account.
You also can't hand this check to a linter. Parliament and CloudFormation Guard analyze local JSON, but they don't evaluate request context or simulate a missing key. The difference between Policies 1 and 2 is beyond what they check.
Next step
Save the three JSON files and walk each one through the User Guide operator table in three cases: key present and inside the range, key present and outside the range, and key missing. For each case, note whether the statement grants. Policy 2 should be the only one that grants when the key is missing. If you have credentials, the Policy Simulator can confirm that reading.