Roles grant; guardrails take away or cap. Every guardrail sits on a directory node and applies to that node and everything below it. None of them can grant a permission.
The guardrails are configured through the API under /api/v1/access/. They have no console page yet.
Deny policies
A deny policy removes permissions in a subtree and is evaluated before any allow.
| Route | Permission |
|---|---|
GET, POST /api/v1/access/deny-policies | iam.denypolicies:read, iam.denypolicies:manage |
GET, PUT, DELETE /api/v1/access/deny-policies/{id} | iam.denypolicies:read, iam.denypolicies:manage |
Body: node_id, name, description, denied_principals and exception_principals ([{"type": "all"}] or [{"type": "user", "id": "..."}]), denied_permissions, exception_permissions, condition, condition_title.
{
"node_id": "<production project node>",
"name": "No vault changes in production",
"denied_principals": [{"type": "all"}],
"exception_principals": [{"type": "user", "id": "<release manager user id>"}],
"denied_permissions": ["vault.secrets:write"]
}
- A deny whose condition is true, or cannot be evaluated, applies. Write conditions so that missing context fails toward denial.
- A deny policy may not deny
iam.denypolicies:manageitself, so administration cannot be locked out. - An exception must name the exact permission, not a broader legacy scope that implies it.
Permission ceilings
A ceiling lists the permissions that may be effective in a subtree. Anything outside it is not effective there, whatever the roles say.
| Route | Permission |
|---|---|
GET, POST /api/v1/access/ceilings; GET, PUT, DELETE /api/v1/access/ceilings/{id} | iam.ceilings:read, iam.ceilings:manage |
Body: node_id, name, description, allowed_permissions. Ceilings do not cap the iam.* permissions that administer access, so a ceiling never locks administrators out of changing it.
Delegation allow-lists
A delegation allow-list names the roles that may be bound on a node and its subtree.
| Route | Permission |
|---|---|
GET, PUT, DELETE /api/v1/access/nodes/{node_id}/delegation | iam.delegation:read, iam.delegation:manage |
Body: {"role_ids": [...]}. The nearest list on the node or an ancestor applies. Where no list applies, the organisation's delegation_default setting decides: closed (the default) lets only organisation-level granters bind roles there; open lets anyone holding iam.bindings:create on the node bind any role.
Separation of duties
A separation-of-duties rule lists two to twenty roles that one person may not hold together in a subtree.
| Route | Permission |
|---|---|
GET, POST /api/v1/access/sod-rules; GET, PUT, DELETE /api/v1/access/sod-rules/{id} | iam.sod:read, iam.sod:manage |
Body: node_id, name, description, role_ids. The rule is checked twice: a binding that would break it is refused (counting group bindings and composite roles), and at check time, if a person holds both sides anyway, the bindings of those roles are ignored at that node.
Access settings
| Route | Permission |
|---|---|
GET /api/v1/access/settings | iam.settings:read |
PUT /api/v1/access/settings | iam.settings:update |
| Setting | Meaning |
|---|---|
inactivity_disable_days | Disable people whose last session activity in the organisation is older than this. null turns it off. |
jit_max_minutes | Organisation maximum for a just-in-time activation |
break_glass_max_minutes | Organisation maximum for a break-glass activation |
approval_timeout_minutes | How long an activation waits for approval |
delegation_default | closed (default) or open |
Disabled people are denied everything in the organisation until an administrator re-enables them: GET /api/v1/access/disabled-users (iam.principals:read) and POST /api/v1/access/disabled-users/{user_id}/enable (iam.principals:manage).
Check what someone can do
| Route | What it answers |
|---|---|
POST /api/v1/access/check | One decision: {"permission", "node_id", "principal"} to {"allowed", "decided_by", "revision"} |
POST /api/v1/access/batch-check | Up to 100 decisions |
POST /api/v1/access/explain | The decision with every step: denies, ceilings and bindings considered |
GET /api/v1/access/effective-permissions | A principal's effective permissions on a node |
GET /api/v1/access/nodes | Where a principal holds a permission, with the denies and ceilings that block part of each subtree |
GET /api/v1/access/principals | Who holds a permission on a node, through which bindings |
These need iam.access:analyze on the node, unless callers ask about themselves. decided_by is one of super_admin, catalog, node, disabled, deny, ceiling, binding, api_key_scope, api_key_owner, sod or no_grant. The analysis uses the same engine as enforcement, so it cannot disagree with it.
Audit
Every change to deny policies, ceilings, delegation lists, separation-of-duties rules and settings is written to the audit log in the transaction of the change. See Audit log.