> Raw Markdown twin (generated at build time from the source Markdown). Rendered page: https://docs.gatellm.io/en/reference/audit-and-security-config · Doc index: https://docs.gatellm.io/en/llms.txt


# Audit and security configuration

This chapter covers the audit records of config changes, and the console's security settings. The audit log has a console page (admin only); a few security capabilities (like manual IP banning) deliberately have no UI — a design trade-off this chapter explains honestly.

## Audit logs

Audit logs record **config-change** operations in the console (create/delete/modify), login/logout, password changes, and other security events, complementing request logs ([Logs and body storage](/en/reference/logs-and-body-storage.md), which record every API call).

**How to view**: after logging in as admin, go to **Settings → Audit** (admin only). The page supports filtering by actor (substring match), action type, entity type, and time range, browsed in reverse-chronological pagination; click any row to view the change detail (diff and metadata). The corresponding API is `GET /console/api/audit-logs` (admin only), see the "Audit logs" section of the Console API docs. Reading audit logs itself does not produce an audit record.

Fields of each audit record:

| Field | Description |
|------|------|
| actor | The initiating subject: console username; `secret_key` (secret-key bearer direct call); `api_key:<mask>` (initiated by an access-key holder, e.g. MCP tool call, context refresh) |
| action | For config entities, CRUD actions like `create` / `update` / `delete`; also `rotate` (access-key credential rotation) / `batch_update` / `batch_commit` / `import` (SSO credentials); for security events, `login.success` / `login.failure` / `logout` / `change_password` / `admin_change_password` / `sessions.revoke_all`; for MCP events with a subject, `tool.call` / `acl.violation` / `mcp.context.refreshed` |
| entity_type | `access_key` / `upstream` / `model` / `model_pricing` / `mcp_server` / `console_user` / `console_session` / `load_balancer` / `lb_static_binding` / `lb_static_binding_pin` / `sso_credential` / `search_provider` (search engine) / `search_config` (search config) / `batch`; MCP tool calls and ACL violations are `mcp`, client-initiated MCP session refresh is `mcp_context` |
| entity_id | The affected entity's identifier |
| diff | The change difference (for update / rotate), truncated when too long; credential fields (`api_key` / `api_keys` / `auth_header`) only record masks, never plaintext |
| ip_address | The actor's IP |
| request_id | The `z-request-id` header (if present) |
| occurred_at | Event time (UTC) |

Audit only records events **with an initiating subject**. Subject-less system self-driven behavior (MCP server startup registration, session eviction) doesn't enter the audit table — it goes to runtime logs. Audit answers "who did what to which"; system self-description belongs to the operator side.

Writes are "fire-and-forget": an audit-write failure doesn't affect the main operation, only alerts. MCP tool calls and ACL violations are also recorded in the audit log. Audit rows **persist in both SQLite and PostgreSQL storage modes** (`STORAGE_MODE` has only these two legal values; there is no configurable memory mode). Note this is orthogonal to the distributed-state backend (the Redis / memory backend determined by whether `REDIS_URL` is configured, managing sessions, rate limits, broadcast) — whether audit persists depends only on the persistence storage backend, unrelated to Redis.

> Note: audit logs are positioned as operational metadata, not equivalent to a full audit system. For stronger guarantees in production-grade compliance auditing, combine with external log collection.

## IP banning

Console login brute-force protection is handled automatically by IP banning:

- Reaching `CONSOLE_MAX_FAILURES` (default 5) consecutive login failures → auto-temporarily ban that IP for `CONSOLE_BAN_DURATION` (default 300 seconds).
- Bans are stored in memory (lost on restart) and in Redis (persistent across instances).

**There is no manual ban/unban entry.** This is intentional: manual IP management is a WAF (Web Application Firewall) responsibility, not the gateway's. For manual IP control, deploy a WAF or reverse proxy in front of the gateway.

## Rate limiting {#rate-limit}

Three layers of rate limiting:

| Layer | Config | Default |
|------|------|------|
| Per access key | `RATE_LIMIT_RPM` / `RATE_LIMIT_WINDOW_SECS` | disabled; when enabled 120 req/min |
| Per IP | `IP_RATE_LIMIT_RPM` | disabled |
| Per upstream / per model | `rpm` / `max_concurrency` / `tpm` / `tps_min_interval_ms` / `wait_timeout_secs` (default 10s) / `initial_tokens` (0–100, default 100) (console database side) | configured as needed |

Read-only snapshot: `GET /console/api/rate-limit-status` returns each layer's current token/concurrency occupancy. Per-key rate limiting is configured via environment variables, per-upstream / per-model in the console, with no separate write API.

Callers hitting a limit receive HTTP `429 Too Many Requests`. Only **per-upstream / per-model** rate limiting carries a `Retry-After` **response header** (seconds, the wait until next availability); the 429 from per-access-key and per-IP rate limiting carries no such header. The three sources of 429 and how to tell them apart are in [Error codes](/en/reference/error-codes.md).

## Access-key masking

The list shows only the first/last 4 chars of a key (e.g. `sk-a***xxxx`). The full value is fetched on demand only via "Copy" (`GET /access-keys/{name}/api-key`); anonymous keys don't support fetching the full value. The upstream's API key is never exposed in the console.

## Password policy

- bcrypt-hashed storage.
- New/reset/change all require ≥ 8 characters.
- Disabling a console user → that user's existing sessions are immediately revoked.

## Security response headers

Console routes uniformly carry:

| Header | Value |
|----|-----|
| `X-Content-Type-Options` | `nosniff` |
| `X-Frame-Options` | `DENY` |
| `Referrer-Policy` | (policy value) |
| `Content-Security-Policy` | (policy value) |

## FAQ

**Q: I can't find the audit-log page in the console?**
The Audit page is in **Settings → Audit**, admin only. If you log in as monitor, the sidebar doesn't show that entry (by design, not a bug).

**Q: How do I manually ban a malicious IP?**
You can't; the gateway provides no manual banning. IP banning is automatic (failure-count triggered). For manual IP control use a WAF/reverse proxy.

**Q: I locked myself out of login?**
Wait for the ban timeout (default 300 seconds) to auto-release. Or have an admin adjust `CONSOLE_MAX_FAILURES`/`CONSOLE_BAN_DURATION` (requires a container restart).

**Q: Can I turn off the access-key list mask to see full values?**
No, masking is mandatory. To get the full value use that row's "Copy" button (fetches it once via API).

**Next**: [Configure Header ACL access rules](/en/howto/configure-header-acl.md) for admission control; [Logs and body storage](/en/reference/logs-and-body-storage.md) for request logs; [Access key and key group fields](/en/reference/access-keys-groups-fields.md) for key management; [Environment variable reference](/en/reference/configuration.md) for all environment variables.
