---
title: "Access guardrails"
---

# Access guardrails

Deny policies, permission ceilings, delegation allow-lists, separation-of-duties rules and access settings in Neural Inverse Govern, which limit what roles can grant.

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`.

```json
{
  "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:manage` itself, 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](https://neuralinverse.com/docs/govern/audit/).
