Policy Engines Compared - What IAM, Cedar, Rego, and Kyverno Do When Nothing Matches, When an Input Is Missing, and When the Engine Never Answers
First Published:
Last Updated:
The question isn't which one is superior. There are countless comparison articles, and the differences in syntax and expressiveness have already been discussed extensively. What a designer actually gets stuck on is an earlier question. When this engine says nothing, does your system let the request through, or stop it? And when a request is denied, how do you answer why?
This article compares evaluation models, not languages. It lines up four engines, but it does not cover their syntax. What it covers is a single point: the same word
deny promises something different in each engine.Three conclusions, stated up front.
First, the four do not sit at the same layer. Which place makes the decision, and which place enforces it, is arranged differently in each of the four. IAM does both inside the AWS API call itself. Amazon Verified Permissions decides across a network, and the caller's application is what enforces the answer. Open Policy Agent evaluates and returns a decision, and its documentation puts interpreting and enforcing that decision on the side that integrated it. Kyverno cuts into the Kubernetes admission path, and the API server is what stops the request. Without this difference established first, none of the comparisons that follow hold.
Second, there are four different responses when nothing matches. IAM returns an implicit deny. Cedar returns a deny at the third step of its procedure. Rego returns
undefined, which is neither false nor deny. And Kyverno simply does not evaluate resources that don't match. It's inaccurate to say that all four default to a denial.Third, all four have a path on which a check that did not run turns into a check that did not stop. Cedar drops an erroring policy out of the decision. IAM treats a statement whose condition did not match as having no effect, and the inverted operators alone match when the value is absent. Rego hands the
undefined straight back to the caller. Kyverno leaves the handling of an expression error to a setting. A forbid or a deny written to close something goes quiet because an input was missing. Each of the four has its own version of this.And this article splits silence into three. When nothing matched, when an input was missing and evaluation broke, and when the engine never answered at all. These are separate problems, and the four engines do not line up on any of them. Designing against the first alone leaves holes in the other two.
All behavior, default values, versions, and availability information presented in this article have been verified against primary sources. The verification date is August 25, 2026.
Table of Contents
- 1. Four Different Things, All Called Policy
- 2. What Each Engine Returns When Nothing Matches
- 3. How Each Engine Resolves a Conflict
- 4. What Happens When an Input Is Missing
- 5. What Happens When the Engine Never Answers
- 6. How to Find Out Why a Request Was Denied
- 7. What Can Be Said Before the Request Arrives
- 8. Reading Four Engines on One Request Path
- 9. Not Which Is Better, but What You Are Choosing
- 10. Version and Lifecycle Notes
- 11. Failure Modes
- 12. Frequently Asked Questions
- 13. Summary
- 14. References
1. Four Different Things, All Called Policy
1.1 Intended Audience
This article is aimed at designers who have found themselves needing to operate multiple authorization systems concurrently.First, it targets readers who are already using AWS IAM and are now considering integrating another authorization engine. They want to define authorization policies with a level of granularity that IAM cannot provide, such as controlling access within an application or determining deployment eligibility within a cluster. They want to understand whether the new system will adhere to the same principles as IAM.
Second, it addresses readers who are already running multiple authorization systems in parallel. They are experiencing situations where a policy approved by one system is denied by another, or vice versa, and they lack the ability to clearly identify which engine and which specific rule is influencing the outcome.
Third, it is for readers who are currently in the process of selecting an authorization system. However, this article does not offer recommendations. Instead, it focuses on outlining the commitments and responsibilities that arise when choosing each system.
This article is not intended for readers who are seeking a basic introduction to authorization. It assumes a foundational understanding and does not cover fundamental concepts such as the principle of least privilege or attribute-based access control.
1.2 Words This Article Has to Pin Down First
This article contains six terms that have different meanings depending on the engine being discussed. If these terms are not clearly defined upfront, the subsequent chapters will become ambiguous.deny is the most problematic term in this article. In IAM, Deny refers to a value written in the Effect field. In Cedar, Deny represents the final decision the evaluation returns. In Rego, deny is a commonly used convention for the name of a rule, rather than a language feature. In Kyverno, Deny is the name of an action specifying what to do when a violation occurs. All four sit at different levels. This article never uses deny bare; it names the engine each time.default also has four different meanings. In IAM, default refers to the implicit deny, representing the initial state of an evaluation. In Cedar, default deny refers to the final branch of the evaluation process. In Rego, default is a keyword used to declare a value when a rule evaluates to undefined. For Kyverno and Kubernetes, it refers to the value adopted when a configuration item is omitted. The same word represents four distinct concepts: a state, a branch, a keyword, and a default value.policy also requires clarification. In IAM, a policy is a JSON document. In Cedar, a policy is a single permit or forbid statement. Rego has no language construct called a policy. In Kyverno, a policy is a Kubernetes custom resource. The granularity of these terms differs significantly. A single IAM policy can contain multiple statements, while a single Cedar policy consists of only one statement.rule is the same story. In IAM the elements inside a policy are called statements, not rules. In Rego a rule is the central construct of the language. In the older Kyverno API a single policy held several rules. Wherever this article says rule, it names the engine the rule belongs to.IAM carries two meanings. One is the AWS service called Identity and Access Management. The other is the general term identity and access management. In this article IAM means only the first. It is never used here as the general term.conflict resolution needs pinning down as well. Where the term appears in other articles on this site, it almost always means conflict resolution in data replication. Real-Time and GraphQL API Architecture with AWS AppSync and Amazon Aurora DSQL Design Decision Guide address the issue of which action to take when write operations to the same data conflict. This is not the subject of this article. This article's focus on conflict resolution concerns situations where multiple policies provide different instructions for the same request, and determining which instruction to follow.One more separation is worth making up front: a language is not an engine. Cedar is a language, and it is also the engine that its reference implementation provides. Amazon Verified Permissions offers that engine as a managed service. Rego is a language, and Open Policy Agent is the engine that evaluates it. This article uses the name of the language when a property of the language is at issue, and the name of the engine or the service when execution and operation are at issue.
1.3 What This Article Does Not Cover
Cedar's grammar is outside the scope of this article. How to write scope clauses, the distinction betweenwhen and unless, entity hierarchy design, and template usage all belong to AWS Verified Permissions and Cedar Policy Language Complete Guide. That same article also holds the design of Amazon Verified Permissions schemas, the operation of policy stores, and the use of the APIs. This article only addresses Cedar in relation to the evaluation process and the points at which that process might fail.The evaluation process for IAM itself is also outside the scope of this article. The seven layers - explicit deny, resource control policies, service control policies, resource-based policies, identity-based policies, permission boundaries, and session policies, together with their order and interaction - belong to IAM Policy Evaluation Logic Step-by-Step. This article focuses on how to apply that reading to the other engines, rather than reiterating the process itself. The IAM column here is a single line of conclusion, handed to that article.
Partial evaluation within Cedar is also outside the scope of this article. Narrowing the list of tools available to an agent before any call happens is the subject of Amazon Bedrock AgentCore Policy Implementation Guide. This article briefly mentions the existence of partial evaluation as a feature of the model and then directs readers to that article for further details.
The validation of container images and the operation of admission controllers are also outside the scope of this article. Signature verification, chains of attestation, and the machinery that lets only verified artifacts deploy all belong to Software Supply Chain Security on AWS. Kyverno appears in this article only as one example of an evaluation model. The operational details of image validation are not included.
This article will not cover the installation procedures, operational procedures, or performance comparisons of each engine. Regarding performance, no measurements have been conducted, so no numerical data will be provided. Pricing will also not be discussed.
Furthermore, this article does not include instructions on how to circumvent policies or examples of manifests that can bypass admission controller checks. It does write about the design errors that differences in evaluation models produce, but it writes them from the side that prevents them, not from the side that exploits them.
1.4 Verification Date and Primary Sources
This article does not have a single, definitive source of truth. The four engines are developed by separate organizations, and the relevant documentation varies depending on the specific claim being addressed.| Claim Type | Primary Source |
|---|---|
| IAM evaluation logic and handling of condition keys | AWS IAM User Guide |
| Amazon Verified Permissions responses and differences from Cedar | Amazon Verified Permissions User Guide and API Reference |
| Cedar decision-making process, error handling, and validation | Cedar Official Reference Guide |
| Formal claims about Cedar | The original paper |
Rego evaluation, undefined values, and decision logs | Open Policy Agent Official Documentation |
| Kyverno policy types, behavior, and reporting | Kyverno Official Documentation |
Default behavior of admission and validationActions | Kubernetes Official Documentation |
⚠ Regarding Cedar, this article draws upon both AWS documentation and the official Cedar documentation. While both cover similar topics, there are discrepancies between them, and these differences are relevant to the subject matter of this article. Section 10.3 takes this up.
The verification date is August 25, 2026. Of the areas this article covers, the Kyverno policy API and Rego syntax both changed generation in the two years before that date. Wherever a description depends on a specific version, this article states the verification date alongside it.
1.5 The Four Sit at Different Points on the Request Path
Before comparing anything, it is worth confirming that the four do not sit at the same layer.Discussions of authorization separate the place where a decision is made from the place where that decision actually stops the work. Whether those two are the same place or different places is what differs across the four.

Amazon Verified Permissions is explicitly called by applications. The application invokes
IsAuthorized, receives a decision, and it's the application's code that determines whether to follow that decision. The service returns the decision and nothing more; it cannot stop the work itself.Open Policy Agent functions similarly, although its official documentation provides more detail. The documentation states that policies can be decoupled from the software service, and further explains:
OPA policies can represent decisions as arbitrary structured data (e.g., booleans, strings,
maps, maps of lists of maps, etc.)
Therefore, Open Policy Agent does not necessarily return
permit or deny. It returns a value, and it's the responsibility of the calling code to interpret that value and determine what action to take. The documentation also notes that Open Policy Agent is not tied to any specific domain model.Kyverno intercepts the Kubernetes admission path. When a request to create or update a resource reaches the API server, the API server invokes a webhook, and based on the webhook's response, it decides whether to allow or deny the request. While Kyverno makes the decision, the API server is ultimately responsible for stopping the request.
This difference in placement is the premise for every chapter that follows.
| Engine | Evaluation Location | Location Where Processing is Stopped |
|---|---|---|
| IAM | Within AWS services | Same location |
| Cedar and Amazon Verified Permissions | Service APIs or libraries integrated into applications | The application that made the call |
| Rego and Open Policy Agent | Sidecar, library, or separate process | The service that made the inquiry |
| Kyverno | Admission webhook | Kubernetes API server |
⚠ Only the first row has the same entity in two columns. With IAM, the decision and enforcement belong to the same entity. For the remaining three, someone must comply after receiving the decision. Chapter 5 starts from this single point.
2. What Each Engine Returns When Nothing Matches
This is one of the central points of this article.Explanations that describe all four as default deny are easy to find. That description is right about two of them, short of words about one, and wrong about one.
2.1 IAM Returns an Implicit Deny
The response from IAM is clear, and it distinguishes itself from an explicit deny. The IAM User Guide states:An implicit denial occurs when there is no applicable Deny statement but also no applicable
Allow statement. Because an IAM principal is denied access by default, they must be explicitly
allowed to perform an action. Otherwise, they are implicitly denied access.
If nothing matches, IAM denies the request. That deny is treated separately from a deny produced by an explicit
Deny. The distinction earns its keep because an implicit deny can be overridden by an allow and an explicit deny cannot. Section 3.1 takes this up in more detail.The IAM side of the discussion stops here. IAM Policy Evaluation Logic Step-by-Step comprehensively addresses where implicit denies are generated and where explicit denies take effect across each of the seven layers.
2.2 Cedar Returns a Deny at the Third Step of Its Procedure
The official reference guide sets out Cedar's decision procedure in three lines.If any forbid policy evaluates to true, then the final result is Deny. Else, if any permit
policy evaluates to true, then the final result is Allow. Otherwise (i.e., no policy is
satisfied), the final result is Deny.
The third step is the case where nothing matched. The Cedar reference guide refers to this characteristic as default deny and explains it as follows:
no request is authorized (decision Allow) unless there is a specific permit policy that
grants it; by default, the decision is Deny.
The difference with IAM lies in how distinctions are made. IAM separates implicit and explicit denies as distinct concepts, but Cedar's decisions are limited to a binary choice of
Allow or Deny, and there is no such distinction. The distinction isn't in the decision itself, but rather in the information accompanying that decision. Section 6.1 takes this up.⚠ This evaluation process comes from Cedar's official documentation, not from an AWS document. Both Amazon Verified Permissions and Policy in Amazon Bedrock AgentCore state in their own documentation that they follow the same process, but the definitive source for this process is Cedar.
2.3 The Rego undefined Is Not a Deny
This is the only one of the four where what comes back is not a decision.Rego can have situations where a rule does not hold a value. The Open Policy Agent documentation refers to this as
undefined and states:Expressions that refer to undefined values are also undefined.
undefined is not false. And it is not a deny. It is a state where there is no value.Consider the following rule:
package authz
allow if input.user.role == "admin"
When evaluating a request that does not contain
user in input, input.user.role becomes undefined, the condition becomes undefined, and the rule allow itself becomes undefined. allow does not become false. It simply does not exist.What matters is how this state appears to the caller. The Open Policy Agent REST API documentation states:
The server returns 200 if the path refers to an undefined document. In this case, the
response will not contain a result property.
The HTTP status is 200. The body does not contain a
result. Therefore, the response will look like this:{}
The final decision depends on how the calling code handles this response. Code that attempts to extract
result.allow from the response and returns false if it's not found will be closed off. Code that returns true in the same situation will be opened up. And neither code throws an exception. The engine is not denying anything. The decision is made by the caller.Rego provides a way to eliminate this state. It is the
default keyword. The Open Policy Agent documentation states:The default keyword allows policies to define a default value for documents produced by rules
with complete definitions. The default value is used when all the rules sharing the same name
are undefined.
The syntax is as follows:
package authz
default allow := false
allow if input.user.role == "admin"
That one line makes
allow always hold a value. Whatever is missing from the input, allow comes back false. Conversely, a Rego policy without this line does not guarantee that it will return a deny.⛔ Therefore, it is incorrect to say that Rego is default deny. The correct phrasing is that it can be written to behave as default deny, and it will not behave as default deny unless it is written that way.
2.4 Kyverno Does Not Evaluate a Resource It Does Not Match
The Kyverno answer comes from a differently shaped question than the other three.Kyverno policies declare which resources they apply to using
matchConstraints. If a resource does not match these constraints, it is not evaluated at all.And where no evaluation occurs, the request follows the Kubernetes default for admission. Kubernetes documentation describes the conditions under which webhooks are invoked as follows:
Each webhook must specify a list of rules used to determine if a request to the API server
should be sent to the webhook.
This list determines whether a request should be sent to the webhook. If it is not sent, the webhook remains silent.
So in a cluster running Kyverno, a resource that matches no policy goes through. This is not a design flaw in Kyverno. It stems from the fundamental nature of the admission control mechanism, which begins by allowing everything by default.
⛔ Do not describe this in the same words as the default deny of IAM and Cedar. In IAM and Cedar, default deny means the starting point from which permissions are stacked up is zero. In Kyverno, matching nothing means the starting point from which prohibitions are stacked up is zero. The direction is reversed.
2.5 You Cannot Say That All Four Are Default Deny
Here is everything above on one sheet.| Engine | Response when no matches are found | Does it close even if the caller takes no action? |
|---|---|---|
| IAM | Implicit deny | Closes |
| Cedar and Amazon Verified Permissions | Deny, at the third step of its procedure | Closes |
| Rego and Open Policy Agent | undefined (HTTP 200 response without a result) | Undetermined |
| Kyverno | No evaluation occurs; the request passes admission | Opens |
Of the four, only two are guaranteed to close.
⚠ And the first two rows deserve a second look, because they are written in the same words. IAM's implicit deny and Cedar's third step both close, but they arrive there for different reasons. IAM closes as the outcome of evaluating seven layers in order and finding that none of them granted anything. Cedar closes as the last branch of a single procedure. Carrying the intuition from one across to the other is what trips you in the next chapter.
3. How Each Engine Resolves a Conflict
There are four possible ways to handle a conflict: precedence, order, union, and error. The four engines are spread across them, and one of the engines does not allow the state of conflict to exist at all.3.1 In IAM an Explicit Deny Cuts Through All Seven Layers
The rule in IAM is simple: an explicit deny always takes precedence.AWS documentation repeats this rule for each policy type. For identity-based and resource-based policies it says the result is the union of the two, and then writes this:
An explicit deny in either of these policies overrides the allow.
For permissions boundaries it says the result is the intersection of the two and places the same sentence there. Service control policies and resource control policies get the same treatment.
Therefore, conflict resolution within IAM remains consistent regardless of the number of layers. While the method of granting permissions may vary by layer, resulting in unions or other combinations, an explicit deny always prevails, regardless of the layer in which it appears.
The whole of this discussion belongs to the published article. Which layer combines with which, and how the rule switches between same-account and cross-account access, is in IAM Policy Evaluation Logic Step-by-Step.
3.2 In Cedar Forbid Beats Permit, and Policies Never Reference Each Other
Cedar's rule, as stated in Section 2.2, is the first line of the three lines mentioned there. Officially, this is termed forbid overrides permit. This means thatforbid takes precedence over permit. The explanation is as follows:even if a permit policy is satisfied, any satisfied forbid policy overrides it, producing a
Deny decision.
It looks like the explicit deny in IAM, but Cedar carries fewer rules. IAM consists of seven layers, and the method for granting permissions varies for each layer. Cedar, however, has no layers. Instead, it has a single collection of policies, through which a single evaluation process runs.
Furthermore, Cedar possesses another characteristic that the other three systems do not. This is the property that policies do not reference each other. The outcome of evaluating one policy does not influence the evaluation of another. Decisions are made solely based on the collection of policies and the request itself. This characteristic forms the foundation for the analysis capabilities discussed in Chapter 7.
3.3 Rego Turns a Conflict Into an Error or a Union, Depending on How You Write It
Rego does not have built-in conflict resolution rules. More accurately, how Rego handles conflicts depends on how the rules are written.Rego rules can take two forms. One form defines a value, holding only one value, which the Open Policy Agent documentation refers to as a complete definition. Define a rule of this form more than once under the same name, and have evaluation produce different values, and Open Policy Agent returns no result.
OPA returns an error in this case because the rule definitions are in conflict.
In other words, the conflict is not resolved. It becomes an error. This is the most distinctive behavior of the four. While IAM, Cedar, and Kyverno all have rules that determine which rule to apply when multiple rules specify different outcomes, this form of Rego rule prevents such conflicts from occurring in the first place.
The other form defines parts of a collection or object. The documentation refers to this as incremental and is written as follows:
When a rule is defined this way, the rule definition is called incremental because each
definition is additive.
In this form, Rego adds the definitions together. Uses like admission control reach for this form most often.
package admission
deny contains msg if {
not input.request.object.metadata.labels.team
msg := "every workload must carry a team label"
}
deny contains msg if {
input.request.object.spec.hostNetwork
msg := "host networking is not permitted"
}
This
deny is not a Rego feature; it is simply the name of a rule. The two definitions add together into a set holding the reasons for denial. The decision to block a request based on whether this collection is empty is made outside of Open Policy Agent.⛔ So it is also incorrect to say that Rego has no
deny. What is accurate is that you can write a rule named deny, and what that name means is an agreement between you and the caller.⚠ And because it is a union,
deny does not go undefined when there are no reasons to deny. Rules using the incremental form will return an empty set if none of the definitions match. An empty set is not undefined. This behavior differs from rules that define a single value.3.4 Kyverno Evaluates Policies Independently and Stops on the First Denial
Conflict resolution in Kyverno is inherent to the nature of Kubernetes' admission control.When several policies match one request, Kyverno evaluates each of them independently. If any one of those policies denies, Kubernetes denies the request. Kubernetes' documentation mentions this behavior twice within its list of admission controllers.
If any of the ValidatingAdmissionPolicy fails, the request fails.
Matching webhooks are called in parallel; if any of them rejects the request, the request
fails.
The webhooks are called in parallel, and one denial fails the whole request. Order does not enter into the result here either.
In outcome this resembles Cedar's forbid overrides permit. In both, one policy on the stopping side is enough to stop the request. However, the underlying principles differ. In Cedar, with no policy granting permission, the request is denied. In Kyverno, with no policy denying, the request goes through. This reversal of direction, as discussed in Section 2.4, is also evident in conflict resolution.
3.5 Precedence, Order, Union, and Error
| Engine | What happens when multiple rules state different things |
|---|---|
| IAM | An explicit deny takes precedence, across every layer |
| Cedar | forbid takes precedence over permit, and policies never reference each other |
| Rego, rule holding a single value | It becomes an error. No decision comes back |
| Rego, rule defining a set in parts | It becomes a union, and the caller does the interpreting |
| Kyverno | Each policy is evaluated independently, and one denial denies the request |
⚠ Note that order appears nowhere in the table. Not one of the four changes its result according to the order in which things were written. Reorder the policies and the decision does not move. This is one of the few properties all four share, and it means a design that leans on ordering does not hold in any of them.
4. What Happens When an Input Is Missing
This is the second of the article's central points.Chapter 2 dealt with the case where no rule matched. This chapter deals with the case where a rule did match, but the value the evaluation needed was not there. The two are different, and the four engines diverge further here than they did in Chapter 2.
The only thing they share is the direction of the outcome. In all four, a check that did not run can turn into a check that did not stop. The shape differs in every one of them, so they are taken one at a time.
4.1 IAM Does Not Match, Except That Inverted Operators Do
What happens to IAM conditions when the condition key does not exist in the request context?AWS documentation states the following regarding references to condition keys that do not have a value:
When you use a variable with no value in the condition element of an IAM policy, IAM JSON
policy elements: Condition operators like StringEquals or StringLike do not match, and the
policy statement does not take effect.
They do not match, and the statement has no effect. So far this matches intuition. The next sentence does not.
Inverted condition operators like StringNotEquals or StringNotLike do match against a null
value, as the value of the condition key they are testing against is not equal to or like the
effectively null value.
The inverted operators match when there is no value. The logic is that if there is no value, then the condition that the value is not equal to the specified value is true.
This asymmetry affects deny statements. Deny statements using
StringEquals will not be triggered if the key is missing. However, deny statements using StringNotEquals will be triggered if the key is missing. Depending on whether the same deny intent is expressed using one of two different ways of writing the condition, the result when the key is missing is reversed.AWS provides a mechanism to explicitly specify this behavior by adding suffixes to condition operators.
The ...IfExists suffix edits a condition operator. It means that if the policy 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.
This allows you to treat the condition as true when the key is missing. When used in deny statements, this will trigger the deny even when the key is missing.
⚠ However, this suffix changes its meaning when combined with an empty condition key. IAM Access Analyzer has an inspection that flags this combination, and its explanation reads as follows:
The IfExists suffix with an empty condition key matches only if the key {{key}} is missing
from the request context.
If you want to check whether the request context is empty, it is recommended to use the
Null condition operator.Further details regarding IAM are available elsewhere. For specific examples of how to write conditions to check for the presence or absence of multi-factor authentication, refer to IAM Policy Evaluation Logic Step-by-Step.
4.2 Cedar Drops the Erroring Policy Out of the Decision
Cedar's response is the most unexpected of the four.The Cedar reference guide states the following regarding access to non-existent attributes:
If an expression accesses an attribute that isn't present, then evaluation produces an error.
The issue lies further on. The page describing how to handle policies that result in errors gives it the name skip on error and states:
if a policy's evaluation returns error, the policy does not factor into the authorization
response; it is skipped.
Those policies do not join the decision. Cedar treats them as though they were not there.
If this behavior lands on a
forbid, it can lead to results that contradict the intended outcome. Suppose a forbid is written to trigger based on an attribute present in the request's context. If that attribute is not provided, the policy will result in an error and be removed from the decision-making process. If any remaining permit policies match, the decision will be Allow. A policy written with the intention of blocking access effectively disappears silently due to the absence of an input.The Cedar reference guide itself provides a specific example. Even where the operator that tests for the presence of an attribute is used, a form that reaches the attribute before that test runs raises the error first, and Cedar skips the whole policy before the test is ever evaluated.
When Amazon Verified Permissions sits in the path, this fact surfaces in the response. The API reference describes the
errors field in the response as follows:Errors that occurred while making an authorization decision, for example, a policy references
an Entity or entity Attribute that does not exist in the slice.
⚠ The decision itself comes back with an HTTP 200. The request is successful, and the
decision field contains either ALLOW or DENY. You can only determine that an error occurred by examining a different field. An application that reads only the decision field carries on without knowing how many policies stepped out of the evaluation.A means of mitigating this issue is the operator that verifies the existence of an attribute. The reference guide states that this operator returns whether a given attribute is defined for an entity or record. The recommended approach is to pass this operator before accessing the attribute. Specific examples can be found in AWS Verified Permissions and Cedar Policy Language Complete Guide.
4.3 In Rego the undefined Propagates and default Catches It
Rego's behavior runs on the same mechanism Section 2.3 described. If a reference has no value, it becomesundefined, and any expression containing it also becomes undefined, causing rules to become undefined.Unlike IAM or Cedar, this is not an exceptional state. In Rego,
undefined is a normal evaluation result, not an error. Errors are only triggered by situations that are inherently contradictory from a language perspective, such as conflicts, as seen in Section 3.3.Therefore, preparing for missing input in Rego means defining
default values. The official documentation states that by ensuring values are always defined, both the policy and the caller can avoid dealing with undefined values.⚠ However, there are situations where
default values cannot be applied. The official documentation states the following regarding functions with default values:A default function will still fail (as in not evaluate, even to the default value) if any of
the arguments provided in the call are undefined. The reason for this is that the arguments
are evaluated before the function is even called, and an undefined argument halts evaluation
at that point.
With an
undefined argument, not even the default value comes back. Rego evaluates arguments before it calls the function, so evaluation halts before the default is reached.This difference breaks the judgment that writing a default on a rule has made it safe. The default takes effect when the body of the rule did not match. When the value being handed to the rule could not be obtained at all, evaluation stops short of the default.
4.4 Kyverno Leaves Expression Errors to a Setting
Kyverno's current policy types define conditions as expressions using CEL. A setting written on the policy settles how an expression that cannot be evaluated gets handled.The documentation for Kubernetes' ValidatingAdmissionPolicy describes
failurePolicy as follows:failurePolicy defines how mis-configurations and CEL expressions evaluating to error from the
admission policy are handled. Allowed values are Ignore or Fail. Ignore means that an error
calling the ValidatingAdmissionPolicy is ignored and the API request is allowed to continue.
Fail means that an error calling the ValidatingAdmissionPolicy causes the admission to fail
and the API request to be rejected.
Both configuration errors and expression errors are subject to this setting. The default behavior is to reject, and the documentation provides an example where a line specifying
Ignore is noted to have a default of Fail.Kubernetes handles an expression that evaluates to false and an expression that errors differently. The first always follows the declared action. The second follows the same action only when
failurePolicy is Fail.A validation that evaluates to false is always enforced according to these actions. Failures
defined by the failurePolicy are enforced according to these actions only if the failurePolicy
is set to Fail (or not specified), otherwise the failures are ignored.
⚠ In that sentence, these actions refers to the
validationActions described just above it.⚠ Furthermore, elsewhere on the same page, this point is described more loosely. There is a sentence that could be interpreted as suggesting that the
failurePolicy determines how to handle expressions that evaluate to false, which contradicts the previous statement. The more precise statement is the one quoted above. The wording is not consistent within the primary source, so both passages have to be read before anything is implemented.⚠ The older Kyverno policy types carried a different behavior in this position. The Kyverno documentation on reports explains one of the reported results,
error, as a variable substitution that failed outside the preconditions. That a value could not be obtained is what stays in the report. That older API is scheduled for removal during 2026, which Section 10.1 takes up.4.5 In All Four, a Check That Did Not Run Becomes a Check That Did Not Stop
| Engine | When the value the evaluation needs is absent | What becomes of the rule meant to stop it |
|---|---|---|
| IAM | The condition does not match and the statement has no effect. The inverted operators alone do match | The result flips according to which operator was chosen |
| Cedar | Evaluation produces an error | The policy steps out of the decision |
| Rego | The undefined propagates | The rule stops holding a value, and default can catch it |
| Kyverno | The expression produces an error | failurePolicy decides |
⚠ All four systems behave silently. In IAM the statement simply has no effect, and IAM records nothing. Cedar returns a decision, with the error appearing in a different field. Rego returns an HTTP 200 status. Kyverno ignores the issue according to its configuration. None of the four systems will halt processing or throw an exception.
Furthermore, all four systems provide mechanisms to mitigate this behavior. IAM offers conditional operators with suffixes, Cedar provides operators to check for the existence of attributes, Rego utilizes
default, and Kyverno employs failurePolicy. All four share the characteristic that, in their default configurations, there are scenarios where they may not err on the safe side.5. What Happens When the Engine Never Answers
This is the third key point of this article, and it addresses an area not typically covered in existing articles or general comparison pieces.Chapters 2 and 4 dealt with cases where the engine evaluated something and returned an answer. This chapter deals with the case where the engine returns no answer at all. The network is cut. The process is down. The response does not arrive inside the time limit.
This issue directly relates to the format presented in the table in Section 1.5. If the location where a decision is made and the location where processing is halted are the same, then this issue does not arise. However, if they are different, there will inevitably be a potential point of failure between the two.
5.1 The Question Does Not Arise for IAM
IAM evaluation happens inside the request the AWS service received. The caller does not wait on an evaluation; it receives the effect of the decision as the result of the API call.Therefore, it is not possible to observe a scenario where only the IAM evaluation fails. If a service's API does not respond, it is an API failure, and the process does not proceed with a missing authorization decision. There is no path where the process can continue without a decision being made.
This isn't because IAM is uniquely robust. It is because the decision and the enforcement belong to the same party. The other three do not possess this characteristic.
5.2 Amazon Verified Permissions Sits Across a Network
Amazon Verified Permissions is a service that applications call. AWS prescriptive guidance describes this role as a policy decision point. It's a point for making decisions, not a point for enforcing rules.So the application's code settles what happens when a call fails. Does it catch the exception and deny access, grant access by default, or retry the request? There is nowhere on the service side to write that choice.
Furthermore, there's a key factor to consider when designing for availability. AWS documentation states that the policy store is created within a single AWS Region and is automatically replicated across the data centers that make up that Region's Availability Zones.
At this time, Verified Permissions doesn't support any cross-region replication.
The policy store belongs to a Region; it is not replicated to another one. If the application runs in several Regions, where to send the authorization question becomes a separate design problem.
⚠ No guidance on how to behave when the call fails was found in the primary sources consulted for this article. The application's designer is the one who decides.
5.3 Open Policy Agent Separates Deciding From Enforcing
Open Policy Agent's documentation separates deciding from enforcing and writes the separation as a procedure. When integrating it as a Go library, the process involves three steps, with the third step being the following line:Interpret and enforce the policy decisions.
Open Policy Agent is responsible for evaluation, while the integrating side is responsible for interpreting and enforcing the results. At the beginning of the same page, it states that Open Policy Agent provides an API that is independent of any specific domain, allowing services to call it to manage and enforce policies.
Furthermore, the Go example on the same page includes a branch to handle cases where no answer is provided. The example is divided into four scenarios: evaluation errors,
undefined results, unexpected data types, and a final determination. One of these scenarios is represented by the following line:// Handle undefined result.
⚠ The content within this branch is intentionally left empty in the official example. It is up to the reader to determine what to include. This relates to how
undefined, as discussed in Section 2.3, manifests in the code.In essence, when Open Policy Agent does not provide an answer, what happens is not Open Policy Agent's responsibility; it is the responsibility of the code that called it.
There is official guidance on deployment strategies based on this separation.
To integrate with OPA outside of Go, deploy OPA as a host-level daemon or sidecar container.
Running OPA locally on the same host as your application or service helps ensure policy
decisions are fast and highly-available.
One reason it recommends placing components on the same host is for improved availability. Since it avoids traversing the network, the number of potential points of failure is reduced. ⚠ This guidance applies when integrating from languages other than Go. When integrated as a Go library, it does not inherently involve separate processes.
⚠ However, this is a deployment strategy for enhancing availability, not a mechanism for determining behavior when no answer is returned. On the same host, a dead process still returns nothing. And, ultimately, it is the calling code that must define how to handle such failures.
Considering Section 2.3 and the concept of
undefined, it becomes clear that Open Policy Agent presents two scenarios that the calling side must handle. One where an answer comes back but holds no value, and one where no answer comes back at all. At the HTTP level, these are distinct. The former corresponds to a 200 status code, while the latter represents a connection failure or a timeout. Either one can be tipped toward an allow or toward a deny.5.4 Only Kyverno Keeps the Answer in a Setting
Kyverno is unique among the four options, as it allows you to define the answer to this question as a declaration.Kubernetes admission webhooks include a field called
failurePolicy, which determines how to handle situations where the webhook call fails. Kubernetes documentation states:If the webhook call times out, the request is handled according to the webhook's failure
policy.
There are two possible values:
Fail, which rejects the request, and Ignore, which allows the request. And by default, it is configured to reject requests (Section 4.4).Kyverno's current policy types inherit this mechanism directly. Kyverno's documentation states:
A ValidatingPolicy is a superset of a ValidatingAdmissionPolicy and contains additional fields
for Kyverno specific features.
⚠ This option carries operational implications, as noted in Kyverno's documentation itself.
This can happen if all Kyverno Pods are down, due typically to a cluster outage or improper
scaling/killing of full node groups, and policies were configure to fail-closed while matching
on Pods.
The heading this sits under describes the symptom of a blocked API server. The documentation describes how the cluster might appear to be failing, and suggests removing Kyverno's webhook configuration as a potential solution. ⚠ The word form
configure in the quotation is as it stands in the original.Therefore,
failurePolicy: Fail is a choice that prioritizes preventing resources from being created while the authorization decision is missing, even if it means accepting the possibility of the cluster becoming unresponsive. Conversely, Ignore represents the opposite choice. Kyverno presents this choice as a configurable option for the user.Of the four options, Kyverno is the only one that explicitly offers this choice. In the other three, the same choice sits buried in the calling code, most often in an exception handler.
5.5 The Three Kinds of Silence Side by Side
Chapters 2, 4, and 5 all covered cases where the engine did not stop the request. However, the reasons for this vary.
| Engine | No Match Found | Evaluation Fails | Engine Does Not Respond |
|---|---|---|---|
| IAM | Implicit deny | The condition does not match. The inverted operators do match | Does not arise |
| Cedar and Amazon Verified Permissions | Deny | The policy steps out of the decision | The application code decides |
| Rego and Open Policy Agent | undefined | The undefined propagates | The calling service decides |
| Kyverno | Not evaluated, and the request passes | failurePolicy decides | failurePolicy decides |
⚠ If you design against the no-match column alone, gaps will emerge in the other two. And Cedar, which appears safest when nothing matches, is the one that opens most quietly when evaluation fails. No engine closes in all three columns.
To offer another way to interpret this table, in the far-right column - when the engine does not provide a response - in three out of the four cases, the decision is not being made by the engine. Instead, it's determined by the application's code, the service making the request, or a webhook configuration.
The policy itself does not describe this behavior. Without knowing where to look, auditing only the policy will fail to reveal this column.
6. How to Find Out Why a Request Was Denied
When a request is denied, how do you find out the reason? Here too, there isn't a single, consistent approach. The differences lie in whether the reason is provided at the time of the decision, if you need to actively request it, and whether you require additional tools or resources.6.1 Amazon Verified Permissions Answers With the Decision
TheIsAuthorized response contains three fields: the decision itself, the policy that led to the decision, and any errors that occurred during the evaluation.{
"decision": "string",
"determiningPolicies": [
{
"policyId": "string"
}
],
"errors": [
{
"errorDescription": "string"
}
]
}
The description of the second field is directly related to the discussion in Chapter 2. The API reference states:
The list of determining policies used to make the authorization decision. For example, if
there are two matching policies, where one is a forbid and the other is a permit, then the
forbid policy will be the determining policy. In the case of multiple matching permit
policies then there would be multiple determining policies. In the case that no policies
match, and hence the response is DENY, there would be no determining policies.
If no matches are found, this array will be empty. The API reference provides an example that includes the response in its entirety.
{
"decision": "DENY",
"determiningPolicies": [],
"errors": []
}
Therefore, implicit and explicit denials can be distinguished not by the decision itself, but by whether this array is empty. As mentioned in Section 2.2, Cedar's decisions are binary and lack distinction; this refers to the distinction living in what accompanies the decision.
The same principle is also stated from Cedar's perspective. If the decision is
Allow, the policy that made the decision is a permit that met the conditions; otherwise, it is a forbid that met the conditions. If the decision results in Deny because no policy met the conditions, the list of policies that made the decision will be empty. Furthermore, if any policy evaluation resulted in an error, the identifier of that policy will be included in the diagnostic information.⚠ An implementation that never reads the third field cannot notice the problem in Section 4.2. A decision came back, so the call succeeded. That a policy stepped out of the decision is written nowhere else.
6.2 Open Policy Agent Makes You Retrieve It Afterwards
The response from Open Policy Agent is a value derived from the document being queried. It does not contain information explaining why that value was determined. The reason must be retrieved separately.There are two methods for retrieving this reasoning. One is to specify it with each request, and the other is to establish an operational system for logging.
Specifying it with each request is achieved through
explain. The REST API documentation lists the possible values that this query parameter can accept.explain - Return query explanation in addition to result. Values: notes, fails, full, debug.
This mechanism returns the evaluation process and is a tool primarily used during development. It should not be included with every request in a production environment.
Establishing an operational system involves using decision logs. The documentation explains what it records as follows:
The decision logs contain events that describe policy queries. Each event includes the policy
that was queried, the input to the query, bundle metadata, and other information that enables
auditing and offline debugging of policy decisions.
⚠ The mechanism only runs once you turn it on. The documentation states that with decision logging enabled, the server puts a unique identifier for each decision into the response.
When decision logging is enabled the OPA server will include a decision_id field in API calls
that return policy decisions.
Furthermore, the input recorded in the logs carries the original request as it stands. To address privacy concerns, mechanisms are provided to redact values and to disable logging altogether.
Put together: in Open Policy Agent, why a request was denied is a question you answer outside the decision. The decision is a value, and a value carries no reason with it. The reason can only be retrieved if a system for logging decisions is already in place.
⚠ And, as seen in Section 3.3, a partial solution to this problem is already implemented. By returning a collection of strings representing the reasons for denial, the value of the decision itself effectively becomes a list of reasons. This is not a feature of the language itself, but rather a design decision.
6.3 Kyverno Puts It in Two Places
Kyverno has two output destinations.The first is the response to admission requests. When a request is denied, the response includes a reason. This reason contains the name of the policy and the name of the rule. The response shown in Kyverno's quick start is as follows:
error: failed to create deployment: admission webhook "validate.kyverno.svc-fail" denied the
request: resource Deployment/default/nginx was blocked due to the following policies:
require-labels: autogen-check-team: 'validation error: label ''team'' is required. Rule
autogen-check-team failed at path /spec/template/metadata/labels/team/'
The policy name, the rule name, the location of the failure, and the reason are all included in a single message. While Amazon Verified Permissions identifies the policy using an identifier, Kyverno returns the name and reason as a plain text message.
The second output is the report. Kyverno writes the results into Kubernetes custom resources. The documentation states that for resources belonging to a namespace, one type of report is generated, while for resources that span the entire cluster, another type is generated. There are five types of results that can be recorded.
| Result | Official Description |
|---|---|
pass | The resource was applicable to a rule and the pattern passed evaluation. |
fail | The resource failed the pattern evaluation. |
skip | Preconditions were not satisfied (if applicable) in a rule, or an applicable PolicyException exists and so further processing was not performed. |
warn | The annotation policies.kyverno.io/scored has been set to "false" in the policy converting otherwise fail results to warn. |
error | Variable substitution failed outside of preconditions and elsewhere in the rule (ex., in the pattern). |
⚠ The last of these is the missing input from Section 4.4. That a value could not be obtained stays as a result of its own, neither a pass nor a fail. It sits in a different place from what Amazon Verified Permissions shows in
errors, but it plays the same part. Of the four, only these two carry the distinction at all.There are two triggers for report generation. These occur when a request is received and when existing resources are periodically scanned. The second is what lets you see, the moment a policy is added, which resources already in the cluster violate it.
⚠ However, the content of the report changes if you enable enforcement mode. Kyverno's documentation states:
Policies configured with spec.rules[*].validate[*].failureAction: Enforce immediately block
violating resources and results will only be reported for pass evaluations.
That a request was blocked goes back in the response to the requester, not into the report. Watch only the reports and the count of blocked requests stays invisible.
⚠ The field name in that quotation belongs to the older API. The documentation on reports was still written in the older vocabulary as of the verification date. The corresponding setting in the current policy types is
validationActions, which Section 10.1 covers.6.4 IAM Answers Through the Simulator and the Audit Trail
The IAM answer does not come back with the decision. The API call fails, and what can be read out of that error message is limited.The means of getting the reason out belongs entirely to the published article. Tracing by hand which layer stopped it, what the simulator can and cannot confirm, and checking against the audit trail all belong to IAM Policy Evaluation Logic Step-by-Step.
This site also provides tools that run within your browser. These include the IAM Policy Simulator (Offline), for manually tracing IAM decisions; the IAM Policy Least Privilege Analyzer, for identifying overly broad policies; the AWS Policy Diff Tool, for reading the difference between two policies statement by statement; and the Amazon Bedrock AgentCore Cedar Policy Builder and Tester, for building and testing Cedar policies.
6.5 With the Decision, or Afterwards

| Engine | Comes back with the decision | Has to be collected afterwards |
|---|---|---|
| IAM | An error response, which does not name the rule | The audit trail, and replaying the request in the simulator |
| Cedar and Amazon Verified Permissions | The determining policy identifiers, and the list of errors | Whatever your own record of the call kept |
| Rego and Open Policy Agent | The value only. No reason travels with it | Decision logs, and the evaluation trace during development |
| Kyverno | Deny response and reason | Reports, which do not carry the requests that were stopped |
⚠ Two of the four return a reason with the decision: Amazon Verified Permissions and Kyverno. The shape differs.
Amazon Verified Permissions returns identifiers, not human-readable text. The caller has to build the mechanism that maps an identifier back to the text of the policy. Kyverno returns a single message containing the policy name, rule name, and reason. This message is directly readable by humans.
⚠ IAM and Open Policy Agent do not return which rule decided, alongside the decision. IAM returns an error, but that error does not name the layer or the statement that stopped it. Open Policy Agent returns the value and nothing else.
⚠ Furthermore, all four engines record that nothing matched in a different way. Amazon Verified Permissions indicates this with an empty array. Kyverno records nothing, because it never stopped the request. Open Policy Agent returns a response without a
result. IAM responds with an implicit deny.7. What Can Be Said Before the Request Arrives
Everything so far has been about after the request arrives. This chapter is about what can be established before it does.⚠ The FAQ in the published article already notes that Cedar can be analyzed statically and that Rego makes that harder. This article does not repeat the fact. What it writes is what being analyzable lets an operation actually do, and what analysis still cannot see.
7.1 What the Cedar Validator Looks At
Cedar includes a validator that checks policies against schemas.The validator compares a policy with a schema to look for inconsistencies.
The reference guide lists the types of errors this validator can detect. These include unrecognized entity types, unrecognized actions, the application of an action to an entity or resource it does not target, incorrect use of operators, unrecognized attributes, and unsafe access to optional attributes. It also identifies type mismatches within operators.
The validator also raises warnings. One of them flags a condition that is always false. Such a policy applies to no request, so it never touches a decision. Amazon Verified Permissions refers to this state as
ImpossiblePolicy and returns it as one of the reasons for validation failure.7.2 What Being Analyzable Changes in Practice
What being analyzable makes possible in concrete terms is written in the abstract of the original paper.Cedar's design has been finely balanced to allow for a sound and complete logical encoding,
which enables precise policy analysis, e.g., to ensure that when refactoring a set of
policies, the authorized permissions do not change.
When modifying a set of policies, it's possible to verify that the permissions allowed remain unchanged. This is the operational significance.
This verification process cannot be performed in the same way with the other three systems. When organizing IAM policies, it's not possible to determine statically whether the set of permitted actions remains the same before and after the reorganization. The same applies when modifying Rego policies. The method for verification is through testing, and the tests only observe the system's behavior with specific inputs.
This capability rests on the policy independence that Section 3.2 described. Because the policies do not reference each other, the meaning of each individual policy settles the meaning of the whole set. This decomposition is not possible with languages that involve mutual referencing.
⚠ Caution is needed regarding the strength of the claims. The paper states that the design is structured to allow for sound and complete logical encoding, and that Cedar was described in Lean, with key properties of the design being proven using an interactive theorem prover. It does not claim that analysis is always possible.
7.3 What the Cedar Validator Does Not Look At
The reference guide clearly states that a validator being sound and not encountering errors at runtime are separate concerns. It also lists the things the validator does not see. Even policies that pass validation can still result in errors for the following reasons:- Integer overflow due to large integer addition.
- References to entities that do not exist at the time of evaluation.
- Incorrect extended-type values.
Regarding the second point, the reference guide states:
Errors due to missing entities. If a policy references an entity that does not exist in the
entities used to evaluate the policy, any attempt to access that entity's attributes will
fail.
This is the very issue described in Section 4.2. A schema describes the attributes an entity possesses. It does not, however, specify whether that entity will actually be provided. If the set of entities provided with each request does not include the entity referenced by the policy, the evaluation will result in an error, and that policy will be excluded from the decision-making process.
Therefore, passing validation does not guarantee that input will be complete. The only guarantee comes from a mechanism, implemented by the caller, to verify that the provider is consistently delivering all necessary entities.
7.4 Rego Has No Types. It Has Checks and Partial Evaluation
Rego is not a language for validating against a schema. The design is predicated on accepting any JSON as input, and there is nowhere in the language to declare the expected shape of that input.Instead, Rego offers inspection capabilities. A page explaining the transition to version 1.0 states:
run opa check, opa check --strict and lint your Rego projects if you have not already done so
to identify issues.
⚠ What these look at is how the policy is written. They do not look at what a set of policies permits.
Furthermore, Open Policy Agent provides partial evaluation. When the caller specifies which inputs are unknown, Open Policy Agent first evaluates the portions that do not depend on those unknown values, and then outputs the remaining portions as Rego policies. Rules that do not depend on unknown values are evaluated and expanded to the calling location, leaving only the rules that do depend on unknown values.
⚠ This feature does not sit in the same place as partial evaluation in Cedar. The primary benefit, as highlighted by the official documentation, is performance: even with increasing data volumes, the evaluation time remains stable. Moreover, the documentation suggests running it offline and storing the results in anticipation of incoming requests.
A separate article on this site covers partial evaluation on the Cedar side. Using it to narrow the list of tools an agent can reach before any call happens is the subject of Amazon Bedrock AgentCore Policy Implementation Guide. This article simply notes that both engines offer the functionality of partial evaluation.
7.5 Kyverno Expressions Are Checked at Creation, but Mostly as Warnings
Kyverno's current policy types are a superset of Kubernetes' ValidatingAdmissionPolicy (see Section 5.4). Validation runs during policy creation, and most of the issues identified are reported as warnings. The following describes the mechanisms within Kubernetes. This article does not cover Kyverno-specific differences.According to Kubernetes documentation, validation is a two-stage process.
When a policy definition is created or updated, the validation process parses the expressions
it contains and reports any syntax errors, rejecting the definition if any errors are found.
Afterward, the referred variables are checked for type errors, including missing fields and
type confusion, against the matched types of spec.matchConstraints.
First, there is a syntax check. If errors are found, the definition itself will be rejected. Second, there is a type check, which identifies issues such as references to non-existent fields or incorrect data types.
⚠ However, the result of the second stage does not reject the definition. Instead, Kubernetes records the result in a field carrying the policy's status, where it appears as a warning. Kubernetes still accepts the policy.
⚠ And where a policy declares several resource kinds as its target, the check runs against all of them. It is possible for a policy to pass validation for one resource type but fail for another.
7.6 Static Checking for IAM Belongs to a Published Article
IAM also has mechanisms that inspect a policy before it runs. Detecting overly broad statements, finding resources reachable from outside, and running continuous checks in CI all belong to AWS IAM Access Analyzer Deep Dive. A catalog of common pitfalls in IAM configurations can be found in IAM Anti-Patterns.This article will not focus on either of these resources. The key point to emphasize is that IAM analysis involves two types of checks: analysis of policy statements and analysis of accessibility. Verifying that the permissions allowed remain unchanged before and after refactoring, as mentioned in Section 7.2, is a different process.
8. Reading Four Engines on One Request Path
8.1 The Order to Read Them In
In an environment utilizing four components, when a request fails, where should you begin your investigation?Referencing the arrangement outlined in Section 1.5, start from the outermost layer. By examining the layers in order, starting with the layer the request initially interacts with, you can avoid unnecessarily investigating subsequent layers if the process stops at an earlier stage.
⚠ However, the following six steps are not a single, continuous chain. Requests to write to the cluster and requests to call application APIs follow entirely separate paths. A single request may not pass through all six steps. The table below outlines the order in which to investigate failed requests.
| Order | Item to Examine | Potential Cause |
|---|---|---|
| 1 | Whether the originating caller actually issued the request | Error in the request path itself |
| 2 | Kubernetes admission | Cluster write operations are being blocked |
| 3 | Authorization inside the application | Decisions made by Cedar or Rego |
| 4 | AWS API calls | Decisions made by IAM |
| 5 | Input to each layer | Issues described in Chapter 4 |
| 6 | Availability of each layer | Issues described in Chapter 5 |
⚠ Steps 5 and 6 sit at the end, but not because they can be put off. They mean that when steps 1 through 4 turned up no reason, these are the two that have to be looked at. As Chapters 4 and 5 showed, both of them happen quietly. No layer's log carries a record that it did not stop something.
8.2 The Fifth Model
Four have been lined up here, and a real environment holds one more.The fifth is the declaration of privilege written in the configuration file of a continuous integration system. In GitHub Actions workflows, this corresponds to the
permissions key. This is a policy, involving an evaluating entity and the possibility of a deny. And the entity performing that evaluation isn't IAM, Cedar, Rego, or Kyverno.This fifth model is the subject of another published article on this site. It explains when the declaration is determined, what the defaults are, and what happens when only a portion of it is defined. You can find this information in Hardening GitHub Actions for AWS Deployments.
One point from it is worth carrying into this article. The article demonstrates that as soon as you specify even a single scope, every scope you did not specify is set to
none. This behavior differs from any of the defaults examined in Chapter 2. Partial declarations are treated as complete declarations. It's not uncommon for the fifth model to already be in operation before you add the fourth engine.8.3 Authentication Comes Before Authorization
Everything in this article comes after the question of who is making the request has been settled.The documentation for Amazon Verified Permissions explicitly states this assumption. The service operates on the premise that the entity requesting access has already been identified and authenticated through other means, and it does not involve itself in how or where that entity is managed or authenticated.
All four components share this same underlying assumption. If this assumption is invalid, the entire authorization design becomes ineffective. How a person identifies themselves is the subject of Passkeys and WebAuthn in Practice. Managing an agent's identifier from registration through retirement is the subject of Identity Lifecycle for AI Agents.
9. Not Which Is Better, but What You Are Choosing
9.1 The Criteria That Decide
No feature list here. Features keep being added to every one of these engines, so a list is out of date the moment it is written. What follows instead is a set of decision criteria: what you take on by choosing each one.The following is based on primary sources as of August 25, 2026.
| Decision Criteria | IAM | Cedar & Amazon Verified Permissions | Rego & Open Policy Agent | Kyverno |
|---|---|---|---|---|
| Does it close when nothing matches? | Closes (implicit deny) | Closes (Deny) | Depends on how it is written (default closes it) | Opens (not evaluated) |
| Does it close when evaluation breaks? | Depends on the operator | It can open (the policy is skipped) | Depends on how it is written | Depends on failurePolicy |
| Can you declare the behavior when the engine does not respond? | Not applicable | Cannot (must be written in code) | Cannot (must be written in code) | Yes (failurePolicy) |
| Does the reason for the decision return at the same time as the decision? | No | Yes (determiningPolicies) | No | Yes (deny response) |
| Are missing inputs logged? | No | Yes (errors) | No | Yes (error result) |
| Can you statically verify that permissions do not change before and after modification? | Cannot | Yes (analyzability) | Cannot | Cannot |
| Is there a place to declare the format of the input? | Official list of condition keys | Yes (schema) | No (arbitrary JSON) | Yes (Kubernetes types) |
| Scope | AWS API calls | Any resource defined by the application | Arbitrary (not tied to a domain) | Kubernetes resources |
| Entity that enforces the decision | AWS | Application that made the call | Service that made the inquiry | Kubernetes API server |
⚠ Six cells in this table are not statements that the official documentation calls something impossible. That the reason does not come back with the decision, that a missing input leaves no record, and that a rewrite cannot be shown statically to leave permissions unchanged are observations that no such mechanism was found in the respective official documentation. They are not assertions. Add such a mechanism and the cell becomes false.
9.2 How to Read the Table
This table does not display an overall score. Within each column, some rows are superior to others, while others are inferior.Cedar is strongest in three rows: the reason coming back with the decision, the missing input leaving a record, and the rewrite being checkable statically. And in the row on what happens when evaluation breaks, it opens the most quietly of the four.
This strength and weakness stem from the same design. Because there is a rule that evaluation errors are not included in policy decisions, the policies can remain independent of each other. It is this independence that allows for static analysis of the entire collection.
Two of Rego's cells say that it depends on how it is written. This is not a weakness, but rather a characteristic traded for a broader scope. Because it does not declare the shape of the input, it can be pointed at any target, and because nothing is declared, the language cannot fix a default behavior.
Kyverno is the only one on the opening side in the first row, and the only one on the can-be-declared side in the third. Admission control stacks prohibitions up, so Kyverno starts out open. Consequently, locations are provided where you can specify how to close it.
IAM lands on the does-not-arise or cannot-be-shown side in most rows. And all of this stems from a single point: that decision-making and enforcement belong to the same entity. When the calling side has no choice, there is also no possibility of making a mistake in that choice.
⚠ And every row in this table composes when the engines are used together. IAM can still stop an operation that Cedar permitted, on the AWS API call that follows. Conversely, a manifest approved by Kyverno does not necessarily guarantee the permissions of the IAM roles referenced within it. An increase in layers does not necessarily mean stronger judgments; it means an increase in the number of judgments.
10. Version and Lifecycle Notes
Two of the four covered here changed generation in the two years leading up to the verification date. This is the point that catches hardest when an old article or an old internal standard is read again.10.1 The Older Kyverno Policy API Is Scheduled for Removal
Kyverno includes both older policy types and newer policy types based on CEL (Common Expression Language).The documentation, as of the verification date, is served with v1.19.0 selected, and it lists five policy types based on CEL. These include policies that inspect Kubernetes resources or JSON content, policies that modify new or existing resources, policies that create or duplicate resources based on conditions, policies that delete matching resources according to a schedule, and policies that validate container image signatures and certificates. All five are marked stable, since v1.18.
They arrived in two waves. According to the documentation, the two validating types came in v1.14 and the remaining three in v1.15.
However, the legacy policy types are scheduled for deprecation. The documentation lists these legacy types, describing them as traditional policy types governing inspection, modification, and generation, as well as image validation. The status column indicates
Deprecated (v1.19), removed in v1.20. The same documentation also states that, as of version v1.19, the CEL-based policy types offer equivalent functionality to the legacy types.The release announcements provide a timeline for the removal of these policies, broken down by version.
| Version | Timeline (as stated in the announcement) | Status |
|---|---|---|
| v1.17 | January 2026 | Marked as deprecated |
| v1.18 | April 2026 | Only critical fixes |
| v1.19 | July 2026 | Only critical fixes |
| v1.20 | October 2026 | Scheduled for removal |
The announcement regarding these policies states:
From this point forward, we strongly recommend that every new policy you write be based on
the new CEL APIs.
⚠ The starting point of deprecation reads v1.17 in the announcement and v1.19 in the documentation's table. The first names the release that marked it; the second reads as the last release carrying full support. Both numbers come from primary sources, so this article does not declare either of them the correct one.
⚠ This transition lands directly on the subject of this article. The names and terminology used for fields that specify what to do when a violation occurs are being changed to align with Kubernetes terminology as part of this transition.
| Generation | Location | Value |
|---|---|---|
| Oldest, deprecated as of 1.13 | One setting for the whole policy | Enforce and Audit, defaulting to Audit |
| Intermediate, since deprecated | Per rule | Enforce and Audit |
| Current | One list for the whole policy | Deny, Audit, and Warn |
Regarding the default setting for the oldest generation, Kyverno's documentation states:
validationFailureAction: controls if a validation policy rule failure should block the
admission review request (Enforce) or allow (Audit) the admission review request and report
the policy failure in a policy report. Defaults to Audit. This field is deprecated as of 1.13.
The current value corresponds to that of Kubernetes' ValidatingAdmissionPolicy. Kubernetes' documentation describes the available combinations as follows:
Deny and Warn may not be used together since this combination needlessly duplicates the
validation failure both in the API response body and the HTTP warning headers.
Furthermore, this setting is required within Kubernetes. The documentation states that each binding must specify one or more actions. The question of what happens if you omit this is not a relevant consideration.
⚠ Be aware that the default setting for older generations was
Audit. A setting that stopped nothing when left unwritten has become a setting you have to write.10.2 Rego Syntax Has Generations
Open Policy Agent changed its syntax in version 1.0. Version 1.0 was released in December 2024.Rules that include a condition now require the
if keyword, and rules with multiple values require the contains keyword. The official documentation explains the reasoning as follows:The contains keyword is required to disambiguate rules that generate a single value from
rules that generate multiple values. The if keyword ensures that the semantics of rules do
not change between v0.x and v1.0 Rego.
⚠ However, the
if keyword is not required for all rules.In OPA v1.0, the if keyword is only required for rules with a declared body. Constants, rules
that only consist of a value assignment, do not require if.
The
if keyword is not necessary for rules that simply assign a value. An example of this is default allow := false, as shown in Section 2.3.Previously, these keywords required an import declaration, which helped ensure that existing policies would not be broken. Since version 1.0, this declaration does nothing.
A compatibility mode is available to support older syntax. However, the official documentation states that this feature is not recommended for most users, as it was designed for scenarios involving the long-term migration of a large number of third-party policies. Tools are also available to assist with migration, including tools that detect older syntax and tools that automatically convert it.
Every code example in this article is written in the current syntax. The newest release as of the verification date is 1.19.1, published on August 17, 2026. Much of the Rego on the internet predates 1.0. Carry it across unchanged and it fails to parse in a current runtime.
10.3 The Cedar Version Number Does Not Agree Across Two Primary Sources
⚠ This section presents only facts; it does not make any definitive claims.AWS documentation states the following regarding the version of Cedar used by Amazon Verified Permissions:
Verified Permissions currently uses Cedar version 4.7.
This does not align with the official Cedar reference guide. That guide states that it references version 4.5. The most recent language version listed on the version history page is 4.5, dated April 23, 2026. Versions 4.6 and 4.7 do not appear on the language version history.
A possible explanation is that the two sets of numbers represent different numbering systems. The version history page lists the corresponding SDK versions for each language version; for language version 4.5, the corresponding SDK versions range from 4.10.0 to 4.12.0. While both the language and SDK versions begin with the number 4, they are independently numbered.
⚠ This article does not determine which number is correct. The only point that may be relevant to the reader is this: the results obtained by running Cedar locally may not necessarily correspond to the results obtained when evaluating it within Amazon Verified Permissions, even if both are based on what appears to be the same version. When validating behavior dependent on a specific version, it is necessary to record both numbers.
10.4 The Gap Between Amazon Verified Permissions and Plain Cedar Is Narrowing
AWS has a page that lists these differences. At the top of that page is the following note:As of May 2026, Verified Permissions has aligned with Cedar and now supports multiple
namespaces.
This indicates that one difference has been resolved.
And one of the remaining differences is closely related to the subject of this article: how templates are handled. Both allow placeholders in the scope, for the principal and the resource only. However, Amazon Verified Permissions further requires that neither principals nor resources can be unrestricted.
permit(principal, action == Action::"view", resource == ?resource);
Plain Cedar accepts that policy. Amazon Verified Permissions rejects it.
⚠ Therefore, a policy that you write and test in native Cedar may not necessarily be accepted as is. AWS keeps the list of these differences, and this article writes only that the list exists. The list itself changes.
11. Failure Modes
11.1 Assuming That All Four Are Default Deny
As the table in Section 2.5 shows, only two are guaranteed to close. Rego depends on how it is written, and Kyverno opens. The place this misunderstanding costs the most is carrying an intuition formed on IAM across to Kyverno. A resource whose kind was never declared as a target did not pass the check. It was never checked.11.2 Reading a Value Out of an Open Policy Agent Response Without Deciding the Fallback
As Section 2.3 showed, the response can arrive without aresult. Code that has not decided which way to fall in that case follows the default of whatever library was used, not the intent of whoever wrote it. The fallback written on a dictionary lookup becomes the authorization decision.11.3 Writing a Cedar Forbid Without Checking That the Input Arrives
As stated in Section 4.2, if the referenced attributes or entities are not passed, the policy will be excluded from the decision-making process. The author intends this to serve as proof that the action was halted. Whether anything actually stopped is written only in another field of the response.11.4 Reading Only the Decision in an Amazon Verified Permissions Response
As stated in Section 6.1, the response contains three fields. An implementation that reads only the decision never learns how many policies stepped out of the evaluation. It also throws away the identifier list, the one reason that travels with the decision.11.5 Assuming That a Condition Key Is Always Present in an IAM Deny
As described in Section 4.1, the behavior when a key is missing varies depending on the operator used. Deny statements written using the equality operator will not trigger if a key is absent. Conversely, deny statements written using the inverted operator will trigger. Because the same intent can be expressed in two different ways, the author may not be aware of this difference.11.6 Treating the Presence of Kyverno as a Closed Cluster
As described in Section 2.4, admission control is a system that accumulates restrictions. Adding one policy closes only the range that policy declared as its target. Simply verifying that a policy is present without examining its scope is meaningless.11.7 Going to Production Without Deciding failurePolicy
As outlined in Section 5.4, this setting represents a choice between prioritizing availability and security. The default setting is to favor security, and the consequence of this is that the cluster may become unavailable. Choosing not to define afailurePolicy means accepting the consequences without making a deliberate decision.11.8 Shipping Without Turning On the Decision Log
As Section 6.2 showed, the Open Policy Agent decision log is a mechanism you turn on. Where logging is off, nothing afterwards can answer why a request was denied. Turning it on later does not bring back the decisions that already happened.11.9 Treating Static Validation as a Runtime Guarantee
As mentioned in Section 7.3, Cedar's verification does not eliminate errors resulting from references to entities that do not exist at the time of evaluation. The schema describes the types of attributes, but it does not specify the values that will be provided. Similarly, Kyverno's type checking, as described in Section 7.5, often produces warnings rather than definitive checks.11.10 Treating More Layers as a Stronger Decision
As the end of Section 9.2 put it, more layers is more decisions. If even one of four layers goes quiet in the way Chapters 4 and 5 described, that layer is the same as absent. What gets counted is not the number of layers but what happens when each of them falls silent.12. Frequently Asked Questions
12.1 I keep reading that all four are default deny. Is that wrong?
That's not accurate. Both IAM and Cedar return a deny if nothing matches. Rego, however, returnsundefined, which is not a deny. Kyverno does not evaluate resources that haven't been declared as targets, so that request will pass. The table in Section 2.5 lists the four different outcomes.12.2 If I write a default value in a Rego policy, is it as safe as IAM or Cedar?
No, it will not provide the same level of security as IAM or Cedar. A default takes effect when none of the rule bodies match. As stated in Section 4.3, in situations where the values passed to the rules are unavailable, the evaluation may stop before the default value is reached. Furthermore, as described in Section 5.3, what happens when Open Policy Agent returns no answer cannot be written in the policy itself.12.3 How do I notice that a Cedar policy dropped out of the decision?
The list of errors in the response. With Amazon Verified Permissions in the path, theIsAuthorized response carries an errors field, and a referenced entity or attribute that did not exist lands there. The decision itself comes back as usual, so an implementation that never reads that field cannot see it. Using Cedar directly, the diagnostics include the identifier of the policy in question.12.4 A resource slipped past Kyverno. Where should I look?
At the declaration of what the policy matches, first. Typically, resources that are bypassed were not initially targeted by the policy. Check whether the resource falls inside the field that decides which resources the policy applies to. Then the declaration of what happens on a violation. Set to record only, the request goes through and leaves nothing but a report entry. As Section 6.3 showed, a report is written on a request arriving and on a periodic scan.12.5 How do I make an IAM deny fire even when the condition key is absent?
The condition operator suffix. There is a suffix that treats the condition as true when the key is absent, and putting it on a deny statement makes the deny fire even with the key missing. Combined with an empty condition key, though, the meaning changes, and IAM Access Analyzer raises a finding. How to write it, and a worked example with multi-factor authentication as the condition, are in IAM Policy Evaluation Logic Step-by-Step.12.6 How do I tell which policy produced the deny in Amazon Verified Permissions?
If the list of determining policies is empty, nothing matched. As Section 6.1 showed, where the deny came from no policy matching, that array comes back empty. Where aforbid matched, its identifier is in there. So whether the array is empty is what separates an implicit deny from an explicit one.12.7 If Open Policy Agent does not answer, should the service allow or block?
This article does not decide it. As Section 5.3 showed, the Open Policy Agent documentation separates evaluating from enforcing and puts the enforcing on the service that called it. So this judgment belongs to the design of the service. What has to be settled is less the judgment itself than that the judgment is written down somewhere explicit. The dangerous state is the one where it is settled implicitly, in the shape of an exception handler.12.8 Should I choose Cedar or Rego?
This article makes no recommendation. What you can use to choose are the nine rows of the table in Section 9.1. The ones that actually decide it are whether you need somewhere to declare the shape of the input, whether you want a rewrite shown statically to have left permissions unchanged, and whether the target is a Kubernetes resource or arbitrary JSON. A comparison of their expressiveness can be found in the FAQ section of the AWS Verified Permissions and Cedar Policy Language Complete Guide.12.9 Do the four contradict each other when they are used together?
No, there will be no conflicts. They compose instead. They do not make simultaneous decisions regarding the same request; instead, they make decisions at different points along the path, as described in Section 1.5. Therefore, a permission granted by one layer does not necessarily mean that the next layer will also grant it. To proceed, it is necessary to pass through all layers. And to report at which layer the process stopped, it will be necessary to read the information in the order specified in Section 8.1.12.10 If I am writing a Kyverno policy today, which API should I use?
Currently, these are based on CEL. As noted in Section 10.1, older policy types are deprecated and are scheduled for removal. Official documentation also recommends that all newly written policies should be based on the new APIs. For migrating what already exists, the official documentation provides a mapping of the legacy fields to their new equivalents.12.11 Does any of this apply to policy engines that are not in this article?
In part, yes. The way the questions are framed carries across. Regardless of the engine, these questions - what to return when there is no match, how it falls when evaluation breaks, who decides when no response arrives, and where the reasoning is taken from - can be asked of any engine. However, the behaviors, default values, and terminology referenced in this article are specific to the four engines mentioned. If you're applying this to a different engine, you'll need to verify the corresponding details in that product's documentation.13. Summary
The four engines don't operate on the same layer. The location where decisions are made and the location where processing stops based on those decisions are all different. Only in IAM do both belong to the same party. For the remaining three, someone must act upon the decision that is received.This article has categorized the scenarios where the engines fail to prevent action into three cases. These are scenarios where nothing matches, where the input is missing and the evaluation breaks down, and where the engine simply does not return an answer.
In the first scenario, only two engines guarantee a preventative action. IAM returns an implicit deny, and Cedar returns a deny at the third step of its procedure. Rego returns
undefined, which represents a state of having no value, and is not a deny. Furthermore, Kyverno does not evaluate resources that haven't been explicitly declared, allowing the request to proceed.In the second scenario, all four engines have a path that allows them to proceed silently. Cedar removes policies that result in an evaluation error from the decision-making process. IAM treats a statement whose condition did not match as having no effect, and the inverted operators alone match when the value is absent. Rego hands the
undefined back to the caller. Kyverno delegates this to its configuration. None of the four engines have a path that results in an exception being thrown and processing being stopped.In the third scenario, only Kyverno can declare an answer as a definitive outcome. A configuration item called
failurePolicy presents the user with a choice between prioritizing availability and security. In the other three, the same choice sits buried in the calling code. Most often in an exception handler.The question of why a request was denied gets four different shapes of answer. Amazon Verified Permissions returns, with the decision, the identifiers of the determining policies and a list of errors. And that list being empty is what says nothing matched. Open Policy Agent returns the value alone; the reason is available afterwards only where the decision log was set up in advance. Kyverno puts it in two places, the report and the response, and the report does not carry what was stopped. The IAM way of answering belongs to the published article.
Each engine handles situations before a request arrives differently. Cedar has the property that it can verify whether the permissions allowed change when a set of policies is modified. However, this verification does not eliminate runtime errors. References to entities that don't exist can still occur, even with policies that have passed verification.
Looking at the whole picture, the same pattern repeats. All four engines contain the word
deny. However, each engine uses that word to make a different promise. And the meaning becomes most obscure when the engine does not explicitly state anything. In an environment where all four are used together, a single misunderstanding can create a critical flaw.An increase in layers does not necessarily mean stronger judgment; it means an increase in the number of judgments. You will need to analyze what happens when each layer falls silent.
The verification date is August 25, 2026. Two of the four covered here changed generation in the two years leading up to it. That things have moved again by the time this is read is a possibility the article does not argue against.
14. References
AWS- Policy evaluation logic - AWS IAM User Guide
- The difference between explicit and implicit denies - AWS IAM User Guide
- IAM policy elements: Variables and tags - AWS IAM User Guide
- IAM policy validation check reference - AWS IAM User Guide
- What is Amazon Verified Permissions?
- Differences between Amazon Verified Permissions and the Cedar policy language
- IsAuthorized - Amazon Verified Permissions API Reference
- Resilience in Amazon Verified Permissions
- Implementing a PDP by using Amazon Verified Permissions - AWS Prescriptive Guidance
Cedar
- Cedar Policy Language Reference Guide
- Authorization - Cedar Policy Language Reference Guide
- Operators - Cedar Policy Language Reference Guide
- Validation - Cedar Policy Language Reference Guide
- Document history - Cedar Policy Language Reference Guide
- Cedar: A New Language for Expressive, Fast, Safe, and Analyzable Authorization
Open Policy Agent
- Philosophy - Open Policy Agent
- Policy Language - Open Policy Agent
- Rego Keyword Examples: default - Open Policy Agent
- REST API - Open Policy Agent
- Decision Logs - Open Policy Agent
- Integrating OPA - Open Policy Agent
- Policy Performance - Open Policy Agent
- Upgrading to v1.0 - Open Policy Agent
- v0 Backwards Compatibility - Open Policy Agent
- Releases - Open Policy Agent
Kyverno
- Policy Types Overview - Kyverno
- ValidatingPolicy - Kyverno
- Validate Rules - Kyverno
- Policy Settings - Kyverno
- Kyverno Quick Start
- Policy Reports - Kyverno
- Troubleshooting - Kyverno
- Announcing Kyverno Release 1.17
Kubernetes
- Validating Admission Policy - Kubernetes
- Dynamic Admission Control - Kubernetes
- Admission Control in Kubernetes
Related Articles on This Site
- IAM Policy Evaluation Logic Step-by-Step - Explicit Deny, RCP, SCP, Resource Policy, Identity Policy, Permission Boundary, and Session Policy
- AWS Verified Permissions and Cedar Policy Language Complete Guide
- Amazon Bedrock AgentCore Policy Implementation Guide - Cedar-Based Agent Authorization and Default-Deny Design
- Software Supply Chain Security on AWS - Signing, Attestation, and Admission Control
- AWS IAM Access Analyzer Deep Dive - External, Internal, and Unused Access Analyzers with Custom Policy Checks in CI
- IAM Anti-Patterns - Real-World Mistakes and Their Root Causes
- Hardening GitHub Actions for AWS Deployments - Workflow Permissions, Untrusted Triggers, Action Pinning, and What the Trust Policy Cannot Catch
- Passkeys and WebAuthn in Practice - Discoverable Credentials, Synced Versus Device-Bound Keys, and Why Account Recovery Decides the Design
- Identity Lifecycle for AI Agents - Registration, Delegated Authorization, Access Review, and What Retirement Leaves Behind
- Real-Time and GraphQL API Architecture with AWS AppSync
- Amazon Aurora DSQL Design Decision Guide
Companion Tools on This Site
- IAM Policy Simulator (Offline) - Client-Side IAM Decision Diamond Evaluator
- IAM Policy Least Privilege Analyzer - Detect Overly Permissive IAM Policies Offline
- AWS Policy Diff Tool - Statement-Level IAM / S3 Bucket / KMS Key Policy Comparison
- Amazon Bedrock AgentCore Cedar Policy Builder and Tester - Client-Side Cedar Authorization Policy Editor and Evaluator
References:
Tech Blog with curated related content
Written by Hidekazu Konishi