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

# Audit Logging

> Every authorization decision is recorded.

Halite writes an audit log entry for every significant action — logins, permission checks, key operations, job runs, settings changes, and more. The log is stored in the database and queryable through both the UI and the API.

## What is recorded

Each entry in the `audit_log` table captures the following fields (from `audit/models.py`):

| Field         | Type                      | Description                                                                                |
| ------------- | ------------------------- | ------------------------------------------------------------------------------------------ |
| `id`          | integer                   | Auto-incrementing primary key (BIGINT on Postgres, INTEGER on SQLite)                      |
| `at`          | timestamp (with timezone) | When the event occurred (UTC)                                                              |
| `user_id`     | UUID or null              | The user who performed the action; null for unauthenticated requests (e.g. a failed login) |
| `action`      | string (≤128 chars)       | The action name, e.g. `auth.login`, `auth.change_password`                                 |
| `resource`    | string (≤255 chars)       | The resource targeted, e.g. `user:admin`, `key:web-01`                                     |
| `args_json`   | JSON or null              | Request arguments, with sensitive fields redacted                                          |
| `salt_jid`    | string or null            | The Salt job ID, when the action triggered a Salt job                                      |
| `decision`    | string (≤16 chars)        | `allow` or `deny`                                                                          |
| `result_code` | integer                   | The HTTP status code of the response                                                       |
| `duration_ms` | integer                   | Response time in milliseconds (default 0 if not measured)                                  |

### Sensitive field redaction

`args_json` is automatically redacted before being written. Any key whose name matches a known sensitive identifier has its value replaced with `[REDACTED]`. The redacted keys include: `password`, `passwd`, `pw`, `secret`, `token`, `api_key`, `apikey`, `authorization`, `new_password`, and `current_password`.

## When entries are written

The `audit/writer.py` `record()` function is called by route handlers at the point of the authorization decision or action outcome. Because it is called explicitly rather than via middleware, coverage is intentional — every route that calls `record()` is audited.

Examples of audited actions:

* **Login** (`auth.login`) — written on both successful and failed login attempts. Failed attempts have `decision = "deny"` and `user_id = null`.
* **Change password** (`auth.change_password`) — written on both success and failure.
* Any other action where a route handler explicitly calls `audit_record(...)`.

### Auditing reads

By default, read-only requests (e.g. listing minions or jobs) are **not** written to the audit log. A reserved `AUDIT_AUDIT_READS` setting exists (default `false`) but is not currently wired into request handling.

## Querying the log

The audit log is available at `GET /api/audit`. Access requires the `view:audit:*` permission, which only the `admin` role holds by default (see [RBAC & Permissions](/concepts/rbac)).

### Query parameters

| Parameter  | Type            | Description                                    |
| ---------- | --------------- | ---------------------------------------------- |
| `user_id`  | UUID            | Filter to entries from a specific user         |
| `action`   | string          | Filter to a specific action name (exact match) |
| `decision` | string          | Filter by `allow` or `deny`                    |
| `since`    | datetime        | Return entries at or after this timestamp      |
| `until`    | datetime        | Return entries before this timestamp           |
| `limit`    | integer (1–500) | Number of entries to return (default 50)       |
| `offset`   | integer (≥0)    | Pagination offset (default 0)                  |

Results are always ordered newest-first (`at` descending). The response includes a `total` count (across all matching rows, not just the current page) alongside the `entries` array.

### Example response shape

```json theme={"dark"}
{
  "total": 142,
  "entries": [
    {
      "id": 142,
      "at": "2024-06-01T12:34:56Z",
      "user_id": "550e8400-e29b-41d4-a716-446655440000",
      "action": "auth.login",
      "resource": "user:admin",
      "args_json": {"username": "admin"},
      "salt_jid": null,
      "decision": "allow",
      "result_code": 200,
      "duration_ms": 0
    }
  ]
}
```

## Related pages

* [Audit feature page](/features/audit) — the in-app audit log viewer
* [RBAC & Permissions](/concepts/rbac) — who can view the audit log
