Spaces:
Runtime error
Runtime error
| title: "Compliance & Audit" | |
| version: 3.8.2 | |
| lastUpdated: 2026-05-13 | |
| # Compliance & Audit | |
| > **Source of truth:** `src/lib/compliance/`, `src/app/api/compliance/` | |
| > **Last updated:** 2026-05-13 β v3.8.0 | |
| OmniRoute records administrative actions, authentication events, provider | |
| credential lifecycle changes, and MCP tool invocations to SQLite-backed audit | |
| tables. This page covers what gets logged, where it lives, how long it is | |
| retained, how API keys can opt out, and how to query the data. | |
| The implementation lives in `src/lib/compliance/index.ts` (T-43 β "Compliance | |
| Controls") and `src/lib/compliance/providerAudit.ts`. Audit writes never throw: | |
| on any failure the call is silently swallowed so audit logging cannot break the | |
| main request flow. | |
| ## What Gets Logged | |
| ### Administrative audit events (`audit_log`) | |
| Every call to `logAuditEvent({ action, actor, target, details, ... })` produces | |
| one row. Action strings follow a `domain.verb` (or `domain.verb.outcome`) | |
| pattern. Confirmed in-tree action types include: | |
| | Action | Source | | |
| | ------------------------------------ | --------------------------------------- | | |
| | `auth.login.success` | `src/app/api/auth/login/route.ts` | | |
| | `auth.login.failed` | `src/app/api/auth/login/route.ts` | | |
| | `auth.login.locked` | `src/app/api/auth/login/route.ts` | | |
| | `auth.login.error` | `src/app/api/auth/login/route.ts` | | |
| | `auth.login.misconfigured` | `src/app/api/auth/login/route.ts` | | |
| | `auth.login.setup_required` | `src/app/api/auth/login/route.ts` | | |
| | `auth.logout.success` | `src/app/api/auth/logout/route.ts` | | |
| | `provider.credentials.created` | `src/app/api/providers/route.ts` | | |
| | `provider.credentials.updated` | `src/app/api/providers/[id]/route.ts` | | |
| | `provider.credentials.revoked` | `src/app/api/providers/[id]/route.ts` | | |
| | `provider.credentials.batch_revoked` | `src/app/api/providers/route.ts` | | |
| | `sync.token.created` | `src/app/api/sync/tokens/route.ts` | | |
| | `sync.token.revoked` | `src/app/api/sync/tokens/[id]/route.ts` | | |
| | `compliance.cleanup` | `src/lib/compliance/index.ts` | | |
| Each entry captures `action`, `actor` (defaults to `"system"`), `target`, | |
| `details`/`metadata` (JSON), `ip_address`, `resource_type`, `status`, | |
| `request_id`, and `timestamp`. Sensitive keys (`apiKey`, `accessToken`, | |
| `refreshToken`, `password`, anything matching `*token`/`*secret`/`*apikey`, | |
| etc.) are recursively redacted to `"[redacted]"` before the row is written. | |
| ### MCP tool calls (`mcp_tool_audit`) | |
| Every MCP tool invocation writes a row through | |
| `open-sse/mcp-server/audit.ts`. Schema (from | |
| `src/lib/db/migrations/002_mcp_a2a_tables.sql`): | |
| | Column | Notes | | |
| | ---------------- | ----------------------------------- | | |
| | `id` | autoincrement | | |
| | `tool_name` | MCP tool identifier | | |
| | `input_hash` | sha256 of input (no payload stored) | | |
| | `output_summary` | short, truncated summary | | |
| | `duration_ms` | wall time | | |
| | `api_key_id` | caller (nullable) | | |
| | `success` | `1` / `0` | | |
| | `error_code` | terminal error code on failure | | |
| | `created_at` | ISO timestamp | | |
| ### Request / usage logs | |
| These are operational telemetry (not strictly admin audit) but share the same | |
| retention pipeline: | |
| - `usage_history` β per-request usage roll-up | |
| - `call_logs` β full per-request log (subject to row-cap, see below) | |
| - `proxy_logs` β proxy traffic log (subject to row-cap) | |
| - `request_detail_logs` β legacy detailed request log (still pruned if present) | |
| ## Storage Schema | |
| `audit_log` is created lazily by `ensureAuditLogSchema()` on first use: | |
| ```sql | |
| CREATE TABLE IF NOT EXISTS audit_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| timestamp TEXT NOT NULL DEFAULT (datetime('now')), | |
| action TEXT NOT NULL, | |
| actor TEXT NOT NULL DEFAULT 'system', | |
| target TEXT, | |
| details TEXT, | |
| ip_address TEXT, | |
| resource_type TEXT, | |
| status TEXT, | |
| request_id TEXT, | |
| metadata TEXT | |
| ); | |
| ``` | |
| Indexes are created on `timestamp`, `action`, `actor`, `resource_type`, | |
| `status`, and `request_id`. Missing columns on legacy DBs are added via | |
| `ALTER TABLE` on demand. | |
| ## Retention & Cleanup | |
| Two separate retention windows are honoured: | |
| | Env var | Default | Applies to | | |
| | --------------------------- | -------- | ----------------------------------------------------------------- | | |
| | `APP_LOG_RETENTION_DAYS` | `7` | `audit_log`, `mcp_tool_audit` | | |
| | `CALL_LOG_RETENTION_DAYS` | `7` | `usage_history`, `call_logs`, `proxy_logs`, `request_detail_logs` | | |
| | `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Row-cap trim for `call_logs` | | |
| | `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Row-cap trim for `proxy_logs` | | |
| `cleanupExpiredLogs()` runs the retention pass. It is invoked on server startup | |
| from `src/server-init.ts` and `src/instrumentation-node.ts`. Each run logs a | |
| `compliance.cleanup` audit event with the per-table delete counts. Proxy/call | |
| log trimming is batched (`BATCH_SIZE = 5000`) to avoid long write locks. | |
| Manual request-history cleanup is separate from retention. The Request Logs | |
| page calls `POST /api/settings/purge-request-history`, which deletes `call_logs`, | |
| legacy `request_detail_logs`, and local request artifacts under | |
| `${DATA_DIR}/call_logs/`. | |
| Defaults are defined in `src/lib/logEnv.ts` | |
| (`DEFAULT_APP_LOG_RETENTION_DAYS = 7`, `DEFAULT_CALL_LOG_RETENTION_DAYS = 7`). | |
| ## `noLog` Opt-Out (per API key) | |
| API keys can be flagged so their downstream call traffic is not logged. The | |
| flag lives on the `api_keys` table (`no_log INTEGER DEFAULT 0`) and is mirrored | |
| into an in-memory set for hot-path lookups. | |
| ```bash | |
| # Create a no-log key (management auth required) | |
| curl -X POST http://localhost:20128/api/keys \ | |
| -H "Cookie: auth_token=..." \ | |
| -H "Content-Type: application/json" \ | |
| -d '{"name": "Privacy key", "noLog": true}' | |
| ``` | |
| Helpers (`src/lib/compliance/index.ts`): | |
| - `setNoLog(apiKeyId, true|false)` β toggle the in-memory entry | |
| - `isNoLog(apiKeyId)` β checked on the request path; falls back to a 30 s | |
| cached read from `api_keys.no_log` | |
| - `NO_LOG_API_KEY_IDS` (env, comma-separated) β preloaded into the in-memory | |
| set on boot; useful when you cannot toggle the column directly | |
| Administrative audit events (login, provider changes, MCP tool calls, etc.) | |
| are **not** affected by `noLog` β only per-request traffic logging is opted | |
| out. | |
| ## REST API | |
| | Endpoint | Method | Description | Auth | | |
| | --------------------------- | ------ | ------------------------------------------ | ---------- | | |
| | `/api/compliance/audit-log` | `GET` | Paginated admin audit entries with filters | management | | |
| | `/api/mcp/audit` | `GET` | Paginated MCP tool audit entries | (open-sse) | | |
| | `/api/mcp/audit/stats` | `GET` | Aggregated MCP audit stats | (open-sse) | | |
| No CSV export endpoint is shipped today β export from the dashboard or query | |
| the SQLite database directly. | |
| ### Querying `/api/compliance/audit-log` | |
| Supported query params (all optional, all use `LIKE %value%` matching for | |
| text filters): | |
| - `action`, `actor`, `target`, `resourceType` (or `resource_type`), | |
| `status`, `requestId` (or `request_id`) | |
| - `from` / `since`, `to` / `until` β ISO timestamps | |
| - `limit` (default `50`, min `1`, max `500`) | |
| - `offset` (default `0`, max `10_000`) | |
| The response is a JSON array. Pagination metadata is returned in headers: | |
| `x-total-count`, `x-page-limit`, `x-page-offset`. | |
| ```bash | |
| curl "http://localhost:20128/api/compliance/audit-log?action=provider.credentials&from=2026-05-01" \ | |
| -H "Cookie: auth_token=..." | |
| ``` | |
| ## Dashboard | |
| The dashboard exposes audit data at **`/dashboard/audit`** | |
| (`src/app/(dashboard)/dashboard/audit/page.tsx`). The page has two tabs: | |
| - **Compliance** (`ComplianceTab.tsx`) β admin audit events from | |
| `/api/compliance/audit-log`. Filters by event type, severity (info / warning | |
| / critical, derived from action + status), and date range. Severity is | |
| computed client-side from the action/status strings. | |
| - **MCP** (`McpAuditTab.tsx`) β MCP tool audit from `/api/mcp/audit`, with | |
| filters by tool name and success/failure. | |
| Both tabs paginate with page sizes of `50` (compliance) and `25` (MCP). | |
| ## Provider Credential Helpers | |
| `src/lib/compliance/providerAudit.ts` provides shaping helpers used by the | |
| provider-management routes when they emit credential events: | |
| - `summarizeProviderConnectionForAudit(connection)` β strips `apiKey`, | |
| `accessToken`, `refreshToken`, `idToken`, and | |
| `providerSpecificData.consoleApiKey` before the connection snapshot is | |
| written to `details`. | |
| - `getProviderAuditTarget(connection)` β composes a stable | |
| `"<provider>:<name|id>"` string for the `target` field. | |
| - `extractProviderWarnings(...payloads)` β scans provider responses for | |
| policy/safety warnings (`[sanitizer]`, `prompt injection detected`, | |
| `content has been filtered`, `safety filter`, `policy violation`) and | |
| surfaces up to 5 hits, each truncated to 400 chars. | |
| ## Best Practices | |
| - Flag API keys handling PII (legal, medical, etc.) with `noLog: true`. | |
| - Tune `APP_LOG_RETENTION_DAYS` / `CALL_LOG_RETENTION_DAYS` to meet your | |
| retention policy. The 7-day defaults are conservative. | |
| - Export the audit table off-platform (`sqlite3 dump`) on whatever cadence | |
| your compliance program requires β no built-in archival exists. | |
| - Track `auth.login.failed` and `auth.login.locked` counts for brute-force | |
| detection. | |
| - When adding new admin endpoints, call `logAuditEvent({ ... })` with a stable | |
| `domain.verb.outcome` action string and pass the request context via | |
| `getAuditRequestContext(request)` so IP and `requestId` are captured | |
| automatically. | |
| ## See Also | |
| - [`docs/security/GUARDRAILS.md`](./GUARDRAILS.md) β PII masking, prompt injection | |
| - [`docs/frameworks/MCP-SERVER.md`](../frameworks/MCP-SERVER.md) β MCP tool catalog and scopes | |
| - [`docs/reference/ENVIRONMENT.md`](../reference/ENVIRONMENT.md) β full env var reference | |
| - Source: `src/lib/compliance/`, `src/app/api/compliance/`, | |
| `src/app/api/mcp/audit/`, `src/lib/logEnv.ts` | |