> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tofunmiadewuyi.com/custos/llms.txt
> Use this file to discover all available pages before exploring further.

# Permissions

> The grant model: who can do what, to which resource.

Access in Custos is **grant-based**. Admins grant; members exercise. A member with no grants can log
in and see nothing.

## Grants

A grant binds a user to one permission on one target:

```json theme={null}
{
  "user_id": "...",
  "permission": "host.access",
  "target_kind": "host",
  "target_id": "..."
}
```

`target_kind` is one of `credential`, `host`, `group`, `set`, `backup`, or `global`. `target_id` is
null for `global` grants and required for every other kind.

Grants are **soft-revoked** — revoking sets `revoked_at` rather than deleting the row, so the trail
survives. A user may hold only one active grant per permission/target combination.

Admins bypass grant resolution entirely.

## Groups cascade

A group contains **resources**, not users. Granting a user a permission on a group cascades to every
resource in it at resolve time, so adding a host to a group immediately extends existing
group-targeted grants to that host.

```
user ──grant──▶ group ──contains──▶ resources
```

Deleting a group revokes the grants that targeted it, in one transaction, and pushes fresh snapshots
to the affected hosts.

<Note>
  Groups are groups of resources. Custos has no concept of a user group or team.
</Note>

## Global vs scoped permissions

Creation is global — there is no resource to scope it to yet. Everything else is scoped to a single
resource, or to a group containing it.

| Permission | Kind | Description |
| - | - | - |
| `credential.add` | global | Create credentials |
| `credential.read` | scoped | View a credential value |
| `credential.update` | scoped | Modify credentials |
| `credential.delete` | scoped | Delete credentials |
| `host.add` | global | Create host enrollment tokens |
| `host.access` | scoped | SSH access to a host |
| `host.manage` | scoped | Rename host, change accounts, or set SSH endpoint |
| `host.revoke` | scoped | Revoke or decommission a host |
| `host.upgrade` | scoped | Upgrade a host agent |
| `host.audit` | scoped | View host access audit |
| `group.create` | global | Create resource groups |
| `group.read` | scoped | View a group and its members |
| `group.manage` | scoped | Rename, delete, or change membership of a group |
| `set.add` | global | Create machine secret sets |
| `set.read` | scoped | View a set and its keys |
| `set.manage` | scoped | Edit, delete, or bind a set |
| `backup.read` | scoped | View backup jobs and run history |
| `backup.manage` | scoped | Create, edit, delete, and run backup jobs |

## How `host.access` reaches a host

Granting or revoking `host.access` recomputes the affected host's authorized-key snapshot and pushes
it immediately over the daemon's live WebSocket — directly for a host target, fanned out for a group
target. Offline hosts reconcile on their next connect.

## User status

Suspending a user kills their sessions and pushes fresh snapshots, cutting SSH as well as API
access. It is reversible. Removing a user deletes their login identities and revokes their grants
and sessions, but keeps the user row, their keys, and their log entries so the audit trail stays
readable.

## Permission requests

A member without `credential.read` on a credential can request it
(`POST /credentials/{id}/permission-requests`); an admin reviews it
(`PATCH /permission-requests/{id}`). Approval creates the grant.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.