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

# Auditing

> Every trail Custos keeps, and which question each one answers.

Custos records two different kinds of thing, and mixing them up is the usual source of confusion:

* **What happened** — someone read a credential, someone logged in over SSH, a grant was created.
* **What is possible** — who *could* reach this credential or host right now, and by which path.

The first is history and cannot change. The second is a snapshot of current state.

## What happened

| Endpoint | Answers | Needs |
| - | - | - |
| `GET /credentials/{id}/audit` | who opened this credential, and when | able to open it |
| `GET /sets/{id}/audit` | edits, reveals, deliveries and **machine reads** of a set | `set.read` |
| `GET /hosts/{id}/ssh-audit` | every SSH login attempt, allowed or denied | `host.access` |
| `GET /hosts/{id}/audit` | field-level changes to the host | `host.audit` |
| `GET /grant-audit` | grants and revokes across the install | admin |
| `GET /audit` | recent credential activity across the install | admin |

The per-resource trails page with `limit` and `cursor`, and filter with `from`, `to`, `user_id`,
`action` and `q`. SSH audit also takes `success` to see only denials.

Two details worth knowing:

* **The set audit includes machine reads.** A `machine_read` or `deliver` row is a host consuming the
  set, not a person. If you only ever see human actions, the set is not actually being used where you
  think it is.
* **Deletes survive their subject.** Deleting a credential writes an audit row with a null credential
  id and the name denormalized onto it, so history stays readable afterwards. Same reason removing a
  user keeps their row and keys.

## What is possible

| Endpoint | Answers | Needs |
| - | - | - |
| `GET /credentials/{id}/access-audit` | who can reach this credential, by which path | admin |
| `GET /hosts/{id}/access-audit` | who can SSH here, with the key fingerprints that would work | — |

Each entry names its path: a direct grant, a resource group, or admin role. One person can appear
more than once — a direct grant plus membership in two groups is three entries, and that is the
honest answer.

<Note>
  Admins are listed explicitly in these results. They bypass grants, so omitting them would make the
  audit lie about who has access — the most dangerous kind of wrong answer an access audit can give.
</Note>

The host version also returns the SSH key fingerprints that would actually be accepted, which is the
difference between "has permission" and "can get in right now".

## Checking what a host is enforcing

The audit tells you what the control plane believes. To confirm a host agrees:

```bash theme={null}
curl -sS -X POST "$CUSTOS_URL/hosts/$HOST_ID/refresh" -H "Authorization: Bearer $TOKEN"
```

The response's `key_count` is how many keys the recomputed snapshot contained. On the host,
`custosd status` reports the cached count. A mismatch means delivery, not permission — see
[Troubleshooting](/custos/custos/operations/troubleshooting).

## Retention and tamper-resistance

Audit tables reject truncation at the database level, so a stray `TRUNCATE` cannot quietly erase a
trail.

Notifications are purged after 30 days. Audit rows are not on that schedule — they are the record.


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