Skip to content

Custom policies ​

Roles, group grants, project permissions and token scopes already decide access (see Authorization). A custom policy is for what those settings cannot say: freeze a service for one group, give a team temporary access, block writes from a network, keep a group of contractors to their own agent sessions.

Requires a Console release with the IAM engine

Custom policies live in the IAM center (Settings → IAM center → Policies) and in the /api/iam/policies API. Both exist only in releases that include the IAM engine.

When to use a policy ​

You want to...Use
Give someone a different level of a serviceThe person's Edit access, or a group's service permissions (no policy needed)
Narrow what someone can do inside one projectProject service permissions (no policy needed)
Narrow what a token can doThe token's scope (no policy needed)
Block a group from a service, whatever their role saysA deny policy
Give a group access for a limited time or from one networkAn allow policy with a time window or an IP range
Let more people approve agent tool callsAn allow policy on Agents · Approve tool calls (guide)
Stop people from seeing or acting on other people's agent sessionsA deny policy on the session actions (guide)
Stop a person or group from opening one portal, or one agent from running in portalsA deny policy that names the portal or agent (guide)

The rules of the game ​

  • A policy is enabled or disabled. There is no other state. To see the effect of a rule before it applies, use Preview impact in the editor (who would be affected) and the Access check, and save the rule disabled until you are ready.
  • Allow or Deny. A deny always wins. An allow can never go beyond a token's scope or a project's limits.
  • Anyone with admin on the iam service (owners and admins by default) can create and edit policies. Only owners and admins can make a rule that allows take effect, because allowing is permission management. A delegated IAM administrator can write denies and can save an allow disabled; trying to save an enabled allow answers 403 permit_requires_admin.
  • A rule is built in the guided editor (who, what, where, when) or written as Cedar text. Either way it is checked against the engine's schema before it is saved.
  • Limits: 500 policies, 50 statements per policy and 500 statements per workspace; 64 KiB per policy and 256 KiB in total. A policy name is at most 120 characters and a description at most 1,000.

Create a policy in the guided editor ​

  1. Open Settings → IAM center → Policies and select Create policy.
  2. Under Basics, name the policy (for example Contractors cannot change agents) and decide whether it is Enabled right away. A disabled policy is kept but never applied.
  3. Choose the Effect: Allow grants access on top of what roles and groups give; Deny removes access, whatever roles, groups or other policies allow.
  4. Fill in Who, What, Where and (optionally) Conditions, described below.
  5. Select Preview impact to see which decisions would change before you save. For a policy that reaches many people the preview checks a sample (up to 1,000 people and tokens, within 5 seconds and 20,000 evaluations) and says so.
  6. Select Create policy. If the policy is enabled, you are asked to confirm: it starts deciding access as soon as it is saved.

Who, what, where, when ​

PartChoices
WhoEveryone, or Specific people: groups, users, API tokens, any signed-in user, any API token. A rule about a person or group also covers the API tokens they own, so a deny cannot be walked around with a token.
WhatRead, Write or Admin on every service (Every service), per service, or Specific actions such as models.write.
WhereAnywhere, or limited to services, projects, and specific portals or agents.
WhenA start and end time (Valid from, Valid until; entered in your browser's time zone and stored in UTC), IP ranges (CIDR; a single address becomes a /32 or /128), and Channels (dashboard for a signed-in session, api for a token, portal for a person using a portal).

The conditions of one rule must all hold for it to apply.

Levels add up in an allow rule ​

In an allow rule Admin includes Write and Read, and Write includes Read, the same way a person's effective level is read. Choosing Admin on a service therefore lets the people it names read, change and administer it. The editor shows the included levels as part of the choice, and the rule that is saved lists all of them.

A deny rule is different: it blocks exactly the levels it names. Denying Write does not block Read, and denying Admin does not block Write.

Administration services are checked as Write

A change on an administration service (members, providers, webhooks, and so on) is checked as Write and additionally needs Admin. To block changes, deny Write; denying Admin alone does not stop them. The editor warns about this.

Cedar text ​

Select Edit as Cedar to write the policy as text. The guided form cannot be recovered from Cedar text afterwards. A rule written as Cedar is taken literally, exactly what the statement names: Cedar treats webhooks.read, webhooks.write and webhooks.admin as three separate actions, so a permit that names only webhooks.admin would let somebody administer a service and still not read it. The editor warns about a permit like that and says which levels to add.

The @managed annotation is reserved for built-in policies and is refused in custom ones. The request context a condition can read includes context.ip, context.channel, context.projectId, context.method, context.path and context.now; the editor lists the attributes it accepts.

What the editor warns about ​

The editor warns about rules that cannot do what they say, before you save:

  • a deny rule on an action nothing in the product asks (models.admin when no route needs it);
  • a rule about a single resource other than a portal, an agent or a Files bucket (the gates decide on services; a deny of that kind is refused);
  • an allow rule about portals (a portal is opened by its access list alone);
  • the Write level in an allow rule, which also covers Agents · Approve tool calls and so appoints approvers;
  • IP rules while TRUSTED_PROXIES is not set (see Client address).

An allow rule that nothing in the product can match is flagged This rule would not do anything; you can still save it, after confirming. A deny rule of that kind is refused, because a deny that never takes effect would be a false assurance.

A rule that cannot be evaluated ​

A deny rule that cannot be evaluated for a request (a condition that reads something the request does not have) counts as a deny for that request. The decision names the rule and the problem, so a rule written to block something can never stop blocking it silently.

The same holds for a broad rule: one that cannot be evaluated for every request it matches refuses all of them, except the workspace owner's access to the IAM center, so the owner can always open Policies, find the rule the decision names and fix or disable it.

A rule that cannot even be loaded (for example after an engine upgrade) is skipped instead, and the engine reports itself as degraded.

Examples ​

Freeze Model Hub changes for a group ​

Deny models.write on the models service for one group. This also covers API tokens owned by the group's members.

json
{
  "name": "Freeze Model Hub for contractors",
  "enabled": true,
  "spec": {
    "effect": "forbid",
    "principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
    "actions": ["models.write"],
    "resources": { "services": ["models"] }
  }
}
bash
curl -X POST https://console.example.com/api/iam/policies \
  -H "Content-Type: application/json" --cookie "token=..." \
  -d '{
    "name": "Freeze Model Hub for contractors",
    "enabled": true,
    "spec": {
      "effect": "forbid",
      "principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
      "actions": ["models.write"],
      "resources": { "services": ["models"] }
    }
  }'

The editor compiles that to:

text
forbid (
  principal,
  action == Action::"models.write",
  resource
)
when {
  (
    principal in [Group::"6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"]
    || (principal is ApiToken && principal.owner in [Group::"6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"])
  )
  && (["models"].contains(resource.service))
};

Read-only audit access for an on-call group during one shift ​

An allow rule with a time window. An owner or admin has to enable it.

json
{
  "effect": "permit",
  "principals": { "groups": ["6b1f0c52-2f0a-4c1e-9c61-3f0d8a1b7e11"] },
  "actions": ["audit.read"],
  "resources": { "services": ["audit"] },
  "conditions": { "validFrom": "2026-11-02T08:00:00Z", "validUntil": "2026-11-02T16:00:00Z" }
}

No writes from tokens that call from a given network ​

json
{
  "effect": "forbid",
  "principals": { "anyToken": true },
  "actions": ["Write"],
  "resources": { "any": true },
  "conditions": { "ipRanges": ["203.0.113.0/24"], "channels": ["api"] }
}

Spec reference ​

The guided form is stored as a spec:

FieldValues
effectpermit or forbid
principals{ "any": true }, or any of users, groups, tokens (lists of ids), anyUser, anyToken
actionsLevel groups Read, Write, Admin; service actions such as models.write; resource actions such as agent.session_view_others
resources{ "any": true }, or any of services, projects, resources (a list of { "type", "id" })
conditionsvalidFrom, validUntil (ISO 8601), ipRanges (CIDR), channels (dashboard, api, portal)

Per spec, at most 500 users, 200 groups, 200 tokens, 600 actions, 100 services, 200 projects, 200 resources and 50 IP ranges. GET /api/iam/catalog lists every service, action, resource type and channel with where each is enforced.

Compile and preview ​

Both calls are read-only, need only read on the iam service and never save anything.

bash
# The Cedar text and the warnings for a draft
curl -X POST https://console.example.com/api/iam/policies/compile \
  -H "Content-Type: application/json" --cookie "token=..." \
  -d '{ "spec": { "effect": "forbid", "principals": { "anyToken": true }, "actions": ["Write"], "resources": { "any": true } } }'
json
{ "cedar": "forbid (...) ...", "ok": true, "errors": [], "warnings": [] }

POST /api/iam/policies/preview evaluates a draft against the workspace's people and tokens (up to 1,000 of them, within 5 seconds and 20,000 evaluations) and reports who would flip from allowed to denied or the other way round. To stay cheap, each person is limited to 240 compile calls, 120 access checks and 12 previews per minute; beyond that the answer is 429 rate_limited with a Retry-After header.

API reference ​

The policy routes authenticate with the dashboard session cookie, not an API token. GET needs read on iam; every change needs admin.

CallSuccessWhat it does
GET /api/iam/policies200 { managed, custom }Built-in and custom policies. With resourceType and resourceId it lists the policies that apply to one resource.
GET /api/iam/policies/{policyId}200One policy.
POST /api/iam/policies201Create. Send name, description, enabled and either spec or cedar. A new policy is enabled unless you send enabled: false.
PUT /api/iam/policies/{policyId}200Change, including enable and disable.
DELETE /api/iam/policies/{policyId}204Delete.

A policy is { policyId, name, description, effect, enabled, spec, cedar, statementCount, ... }. Errors are { error, code, details? }:

StatuscodeMeaning
400validationThe body or the policy is not valid. details carries a message and, for Cedar text, a line and col. Also sent when you use the retired mode field with monitor (monitor_removed).
403permit_requires_adminAn enabled allow rule needs an owner or admin. Save it disabled, or ask one.
404not_foundNo such policy.
409conflictA policy with this id already exists.
409limit_reachedThe workspace reached the limit on policies, statements or total size.
429rate_limitedToo many compile, access-check or preview calls. Wait for Retry-After.
503engine_unavailableThe policy engine cannot run on this server.

Every create, change and delete is written to the audit trail (IAM center → Audit, policy changes) with the new Cedar text.

Troubleshooting ​

My allow rule saves but nobody gets access. An allow rule never goes beyond a token's scope or a project's limits, and it cannot open a portal that the portal's access list does not name. Run the Access check for the person and action: it names the rule that decided.

My deny rule does nothing. Check the editor's warnings. A deny on an action nothing asks (for example models.admin) or on one individual resource that no gate asks about has no effect; the editor refuses a deny of that kind. Services are checked at Read and Write, and changes to administration services need Write plus Admin, so deny Write to block changes.

I locked everybody out. Sign in as the workspace owner. The owner's session can always reach the IAM center, so you can disable or delete the rule. See Owner safety.

A person or group the policy names has been deleted. Saving a changed spec checks the people and groups it names against the directory, but a spec that is exactly the stored one is not re-checked, so you can still disable or delete the policy.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub