Skip to main content

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.

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):
FieldTypeDescription
idintegerAuto-incrementing primary key (BIGINT on Postgres, INTEGER on SQLite)
attimestamp (with timezone)When the event occurred (UTC)
user_idUUID or nullThe user who performed the action; null for unauthenticated requests (e.g. a failed login)
actionstring (≤128 chars)The action name, e.g. auth.login, auth.change_password
resourcestring (≤255 chars)The resource targeted, e.g. user:admin, key:web-01
args_jsonJSON or nullRequest arguments, with sensitive fields redacted
salt_jidstring or nullThe Salt job ID, when the action triggered a Salt job
decisionstring (≤16 chars)allow or deny
result_codeintegerThe HTTP status code of the response
duration_msintegerResponse 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).

Query parameters

ParameterTypeDescription
user_idUUIDFilter to entries from a specific user
actionstringFilter to a specific action name (exact match)
decisionstringFilter by allow or deny
sincedatetimeReturn entries at or after this timestamp
untildatetimeReturn entries before this timestamp
limitinteger (1–500)Number of entries to return (default 50)
offsetinteger (≥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

{
  "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
    }
  ]
}