Cedar Policies
Griptape Nodes permission templates use Cedar, Amazon's open-source authorization language. In the Permission Editor, the Permission Builder turns your choices into Cedar. Use this page when you need to write a Raw Cedar template or audit the Cedar generated by the builder.
Choose Raw Cedar only when the builder's capability catalog cannot express the rule you need.
Policy inputs
Cedar makes each decision from four inputs: (principal, action, resource, context). Griptape Nodes maps each authorization checkpoint and its license data into these Cedar inputs:
| Part | What it holds | Use it for |
|---|---|---|
principal |
A fixed placeholder, User::"<anonymous>". |
Nothing. Leave it unconstrained. |
action |
The authorization checkpoint, e.g. Action::"LoadLibrary". |
Naming which operation a rule covers. |
resource |
The checkpoint's resolved library, node type, project, model, or codec. | Matching a specific thing, or a fact about it. |
context |
Active project, engine, library, and license facts. | Scoping a rule to one project, or to a license type. |
Combining decisions
Cedar follows two rules when it combines templates:
- At least one matching
permitmust allow the operation. - A matching
forbidoverrides everypermit, including permits from other templates.
The builder produces these Cedar patterns:
- Exploration (Allow all) starts with
permit(principal, action, resource);, then addsforbidstatements for exceptions. - Production (Deny All) omits the catch-all permit and adds
permitstatements for allowed operations.
Default deny and hard blocks
Cedar denies an operation unless at least one statement permits it. A policy set containing only forbid statements therefore denies every operation, not just the ones those statements match. To build a blocklist, pair the forbids with a catch-all permit:
permit(principal, action, resource);
forbid(principal, action, resource)
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };
Omitting a permit is not a hard block. A permit from another template can allow the operation. Use forbid when no permit should override the denial. Cedar evaluates all templates attached to a license key as one policy set, so the permit and forbid can live in separate templates.
Checkpoints
Each action below identifies an operation that the engine gates. The result of a denial depends on the checkpoint.
| Action | Fires when | Resource | A denial looks like |
|---|---|---|---|
Action::"LoadLibrary" |
A library loads past its metadata stage. | Library |
The library is marked unusable, with the reason on its error icon. |
Action::"InstantiateNode" |
A node is created. | NodeType |
An Error Proxy node in place of the real one. Denied node types are also listed on the library ahead of time. |
Action::"LoadProject" |
A project template is read. | Project |
The project fails to load. |
Action::"ActivateProject" |
A project becomes the current one. | Project |
The switch fails and the current project stays put. |
Action::"OfferModel" |
A model picker is built. | Model |
The model is filtered out of the picker. |
Action::"InvokeModel" |
A node calls a model. | Model |
The invocation fails. |
Action::"ReadVideoCodec" |
Video is about to be read, and when a picker is built. | VideoCodec |
The read is refused, or the codec is filtered out. |
Action::"WriteVideoCodec" |
Video is about to be written, and when a picker is built. | VideoCodec |
The write is refused, or the codec is filtered out. |
Resource attributes
Unless a table says otherwise, each resource attribute is optional. Use has before reading an optional attribute. See Guard optional attributes for the required pattern.
Library
| Attribute | Type | Present when |
|---|---|---|
id |
string | Always. The library name. |
lifecycle_stage |
string | The library declares a stage. |
NodeType
| Attribute | Type | Present when |
|---|---|---|
id |
string | Always. The node type name. |
executes_arbitrary_code |
bool | Always. true when the node's library declaration marks arbitrary Python execution. |
lifecycle_stage |
string | The node declares a stage, or inherits one from its library. |
model_ids |
set |
The node declares model usage. |
provider_ids |
set |
The node declares model or provider usage. |
model_families |
set |
The node's declared models resolve to families in the library's model catalog. |
Project
| Attribute | Type | Present when |
|---|---|---|
id |
string | Always. The project's opaque id, a GUID for projects created in the editor. |
name |
string | The template has loaded far enough to know its name. |
Use name for a human-readable rule and id when the match must be exact. Prefer id in a permit. The project name becomes available only after the template has loaded, and a permit that does not match results in a denial.
Model
| Attribute | Type | Present when |
|---|---|---|
id |
string | Always. The stable catalog model key. |
provider_id |
string | The key resolves in the model catalog. |
model_families |
set |
The resolved model declares a family. Carries that one family. |
| To match a provider rather than a single model, see Entity hierarchy. |
VideoCodec
| Attribute | Type | Present when |
|---|---|---|
id |
string | Always. The codec name as probed, e.g. h264, hevc. |
container_format |
string | The container was known, e.g. mp4, mov. |
Entity hierarchy
ModelProvider
Each Model sits under the provider that offers it, so in can cover all of a provider's models without naming them individually.
For example:
forbid(principal, action, resource)
unless { resource in ModelProvider::"anthropic" };
Provider ids are the provider keys in a library's model_catalog, such as anthropic or ollama.
Context facts
Guard context facts
All context facts are optional. Griptape Nodes includes the facts it can resolve and omits the rest, so check each one with has before reading it.
Guard the record, then read through it:
when {
context has loaded_libraries &&
context.loaded_libraries.names.contains("My Library")
}
| Fact | Type | Notes |
|---|---|---|
active_project.id |
string | Opaque project id used as the engine registry key. |
active_project.name |
string | Its display name, when the template has loaded. Needs its own has guard. |
engine.id |
string | The active engine's id. |
loaded_libraries.names |
set |
Names of the libraries loaded so far. |
license_id |
string | The license key's id. |
org_id |
string | The organization the license belongs to. |
license_type |
string | Either "headless" or "interactive". |
Project scope
The builder adds this condition to every statement in a Project-scoped template, using the linked project:
when { context has active_project && context.active_project.id == "<project id>" }
Griptape Nodes evaluates the policy for each project in the active project's chain. This has two consequences.
Include ancestor projects. When a project inherits from a parent, the policy runs for each project in the chain: first the active project, then each ancestor. Every run must allow the operation. A forbid scoped to a parent therefore blocks its children, and a project allow-list must include every project in the chain.
For example, suppose Shot 42 inherits from Studio Defaults. Allowing libraries in Shot 42 requires a permit for both project ids:
permit(principal, action == Action::"LoadLibrary", resource)
when {
context has active_project &&
context.active_project.id == "<Shot 42 id>"
};
permit(principal, action == Action::"LoadLibrary", resource)
when {
context has active_project &&
context.active_project.id == "<Studio Defaults id>"
};
With only the first permit, the Shot 42 check succeeds, but the Studio Defaults check is denied. A forbid matching Studio Defaults also denies the operation in Shot 42.
Account for the default project. Unless otherwise set, the engine boots into the default project, whose id is <system-defaults>. You do not need to explicitly permit this id. Explicit forbid rules still apply.
Denial annotations
Cedar identifies the rule that denied an operation, but it does not explain what the user is missing. Griptape Nodes uses three annotations to add that information. All three are Griptape conventions, and Cedar ignores them during policy evaluation.
| Annotation | Purpose |
|---|---|
@id("<slug>") |
Stable name for the rule. Shown in the denial instead of a positional fallback. |
@capability("<name>") |
What the user lacks, e.g. arbitrary-code-execution. |
@advice("<text>") |
What to do about it. This is the sentence the user reads. |
@id("nodes/no-arbitrary-code")
@capability("arbitrary-code-execution")
@advice("Nodes that run arbitrary code are not available on this license. Ask your studio admin to enable them.")
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has executes_arbitrary_code && resource.executes_arbitrary_code };
Add these annotations to every forbid you write. Without @advice, the user sees a policy id but no instruction for resolving the denial.
The value of @capability is free-form, and Cedar does not validate it. Consider reusing the names generated by the builder to keep denials consistent across templates:
librarylibrary-lifecyclenode-lifecyclearbitrary-code-executionprojectmodelmodel-providermodel-familyvideo-codec
Examples
Each example below is a complete Raw Cedar policy.
Block arbitrary-code nodes
This example includes the catch-all permit, making it a complete blocklist template:
permit(principal, action, resource);
@id("nodes/no-arbitrary-code")
@capability("arbitrary-code-execution")
@advice("Nodes that run arbitrary code are not available on this license.")
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has executes_arbitrary_code && resource.executes_arbitrary_code };
Block unstable libraries and nodes
Use separate statements for libraries and nodes. Each denial then names the capability the user lacks.
permit(principal, action, resource);
@id("lifecycle/no-experimental-libraries")
@capability("library-lifecycle")
@advice("Experimental libraries are blocked on this license. Ask your studio admin which libraries are approved.")
forbid(principal, action == Action::"LoadLibrary", resource)
when {
resource has lifecycle_stage &&
(resource.lifecycle_stage == "LABS" || resource.lifecycle_stage == "ALPHA")
};
@id("lifecycle/no-experimental-nodes")
@capability("node-lifecycle")
@advice("Experimental nodes are blocked on this license. Use a STABLE or BETA alternative.")
forbid(principal, action == Action::"InstantiateNode", resource)
when {
resource has lifecycle_stage &&
(resource.lifecycle_stage == "LABS" || resource.lifecycle_stage == "ALPHA")
};
Allow two model providers
The unless clause inverts the match, blocking every provider except the two named below. The in operator returns false, rather than an error, when a resource has no matching ancestor, so it does not need a has guard.
Name both model checkpoints when restricting models. OfferModel removes denied models from pickers. InvokeModel blocks nodes already bound to them.
permit(principal, action, resource);
@id("models/approved-providers")
@capability("model-provider")
@advice("Only Anthropic and OpenAI models are approved on this license.")
forbid(principal, action in [Action::"OfferModel", Action::"InvokeModel"], resource)
unless { resource in ModelProvider::"anthropic" || resource in ModelProvider::"openai" };
Block a model family
Because model_families is a set, use contains to match a value:
permit(principal, action, resource);
@id("models/no-claude-3")
@capability("model-family")
@advice("Claude 3 models are retired on this license. Use Claude 4.")
forbid(principal, action in [Action::"OfferModel", Action::"InvokeModel"], resource)
when { resource has model_families && resource.model_families.contains("Claude 3") };
Allow specific projects
The engine gates project loading and activation separately, so the rule names both checkpoints.
permit(principal, action, resource);
@id("projects/approved")
@capability("project")
@advice("This project is not on your license. Ask your studio admin for access.")
forbid(principal, action in [Action::"LoadProject", Action::"ActivateProject"], resource)
unless {
resource has id &&
(resource.id == "8f2c1a04-9d3e-4b7a-9f10-2c5d6e8a1b33" || resource.id == "c07b5e91-4a2d-4f88-bd63-1e9f7a205c48")
};
Restrict codec writes
This statement names only the write checkpoint.
permit(principal, action, resource);
@id("video/no-prores-writes")
@capability("video-codec")
@advice("Writing ProRes is not permitted on this license. Render to H.264 instead.")
forbid(principal, action == Action::"WriteVideoCodec", resource)
when { resource has id && resource.id == "prores" };
Scope a rule to one project
Cedar combines multiple when clauses with AND, so you can keep the project scope separate from the resource condition:
permit(principal, action, resource);
@id("show-a/no-experimental-nodes")
@capability("node-lifecycle")
@advice("Show A is locked to stable nodes.")
forbid(principal, action == Action::"InstantiateNode", resource)
when {
context has active_project &&
context.active_project has name &&
context.active_project.name == "Show A"
}
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };
Restrict headless licenses
permit(principal, action, resource);
@id("headless/no-model-invocation")
@capability("model")
@advice("Headless licenses cannot call models on this plan.")
forbid(principal, action == Action::"InvokeModel", resource)
when { context has license_type && context.license_type == "headless" };
Gotchas
Guard optional attributes
Reading an attribute that is missing produces an error. Cedar treats a condition containing an error as unsatisfied, while the engine denies any operation it cannot evaluate cleanly. An unguarded read can therefore make a forbid miss resources without the attribute and block valid operations that do not carry that fact.
// Wrong. Denies every node type with no declared lifecycle stage.
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource.lifecycle_stage == "LABS" };
// Right.
forbid(principal, action == Action::"InstantiateNode", resource)
when { resource has lifecycle_stage && resource.lifecycle_stage == "LABS" };
Because && short-circuits, put the guard first.
Guard nested facts at each level. context has active_project does not guarantee that the active project has a name:
when {
context has active_project &&
context.active_project has name &&
context.active_project.name == "Show A"
}
Check names for typos
The engine does not validate Cedar policies against a schema. Typos such as resource.lifecycle_stge or Action::"LoadLibrry" parse successfully but match nothing, leaving you with a rule that appears not to work.
Permit every required checkpoint
If a Production template permits only LoadLibrary, library loading works, but default deny blocks other checkpointed operations. Name every operation the workflow needs, or attach another template that permits them.
// Permits only library loading.
permit(principal, action == Action::"LoadLibrary", resource);
// Broad permit covering every current checkpoint.
permit(principal, action in [
Action::"LoadLibrary",
Action::"InstantiateNode",
Action::"LoadProject",
Action::"ActivateProject",
Action::"OfferModel",
Action::"InvokeModel",
Action::"ReadVideoCodec",
Action::"WriteVideoCodec"
], resource);