Security audit log

Specivo keeps a security audit log of sign-ins, access changes, administration, single sign-on and provisioning, and everything AI agents do over MCP. Every install records it; there is nothing to turn on. Administrators read it under Admin → Audit log or through the API.

What is recorded

Each row has a time, an event type (a code such as login_failure or scim_user_created), the account that acted, and where the request came from: the client address, the request id and the browser or client name (user agent). Rows about a project or an object name those as well. The details hold ids, keys, counts and short names; see What the details hold.

The viewer groups event types into categories:

Category What it holds
Sign-in Password sign-ins and failures, refused credentials (auth_failure), password resets and changes, email address changes.
Access Refused permission checks (access_denied), resource views through the API, searches, project membership and group changes, anonymous access settings.
Agents What only agents do over MCP: reading and listing issues, wiki pages, projects, members and versions, and creating or changing issues, comments, wiki pages, versions, relations, recurring patterns, time entries and AI subscriptions.
Issues and wiki Attachments, wiki page deletion and restore, sprint start and completion, tags, discarded timers. Rows written by an agent are marked MCP.
Administration Project key renames, repository integrations moved to another provider, metadata schema changes, deactivated accounts, currencies switched on or off, reporting currency changes, exchange rates added, edited, deleted or imported, exchange rate settings and the rate fetch switched on or off, AI model prices added, changed or deleted, bundled prices loaded, other people's AI subscriptions added, changed or deleted by an administrator, and AI subscription self-service switched on or off. Admin actions on users (created, locked, password reset, API keys) are under Access as member_change.
Single sign-on Single sign-on sign-ins and failures, accounts created, linked or unlinked, role sync, provider configuration.
Provisioning SCIM user and group changes, refused SCIM requests, SCIM token changes.
Sessions Revoked sessions, single sign-on logouts (including back-channel), sessions the identity provider refused to renew.
AI usage AI usage logged in another person's name, entries changed by someone other than their owner, entries excluded from reports or included again, entries deleted, costs recomputed after a price change, and AI usage logged or changed by an agent over MCP.

Every MCP tool call is recorded with source: mcp in its details, including the client address, request id and user agent of the call.

What is not recorded

  • People's edits to issues and wiki pages. Editing an issue, commenting or changing a wiki page in the web interface or through the REST API is not an audit event. Those changes are in the issue's history and the wiki page's versions, with who made them and when. The same edits made by an agent over MCP are in both places.
  • Successful permission checks. Opening a page you are allowed to see writes nothing; a refused check does (access_denied).
  • Requests without credentials. A signed-out browser tab, a crawler or a port scanner calling the API writes nothing, and neither does a browser session that simply expired.
  • Repeated refusals. A credential that is presented and refused (a bad token, or an invalid, inactive or expired API key) is recorded as auth_failure once a minute per client address and reason, not once per request. API and MCP refusals are counted apart, and those from the MCP endpoint are marked MCP.
  • Anonymous reading of public projects.

Reading it

Admin → Audit log (/admin/audit-log/) lists events newest first. Filter by category, event type, user login, project key, a date range and client address; the dates are days in your timezone and the page starts with the last 30 days. Times are shown in your timezone. Show on a row opens its details, the request id and the user agent. A user or project that has been deleted since shows as # and its id.

The same data is at GET /api/v1/admin/audit-logs/, with the filters event_type, event_types (repeat it for several), user_id, login, project_id, ip_address, after, before, offset and limit. Each item carries user_login and project_key. An unknown event type or login answers 422.

Only instance administrators can read the log. An administrator's API key works for the API; a key limited to some projects does not.

Retention

Rows are kept for 12 months by default, counted in whole calendar months: on 4 November 2026 the oldest kept day is 1 November 2025. A daily job (also run when a worker starts) drops older months. Change it with an environment variable, then restart the stack:

Variable Meaning
AUDIT_LOG_RETENTION_MONTHS Whole months of audit log to keep before the current one. Default 12. 0 keeps everything.

The setting is deliberately not in the admin pages: how long security evidence is kept should not be changeable from an administrator's session or API key.

The log is stored in monthly partitions of the security_audit_logs table, so dropping a month is quick. If the job cannot get a lock on the table within five seconds it gives up and tries again the next day. The log is part of the database, so the backup includes it.

What the details hold

Ids, keys, slugs, names and titles of objects (file names, page titles, sprint names), codes, counts, logins of accounts, group names, and search text cut to 500 characters. Never tokens, secrets or anything typed as a password, and never the text of issues, comments, descriptions or wiki pages.

A failed sign-in records the login only when it names an existing account; otherwise the row says login_known: false, so a password typed into the login field by mistake is not stored. A password reset request treats the address the same way: it is stored only when it matches an account, otherwise the row says email_known: false.

Email address changes

Event Account Details
email_change_requested the person new_email, address_in_use (another account uses it: no link was sent), resend
email_change_failed the person reason: a wrong password, a missing fresh sign-in at the identity provider, or a confirmation link that was used, replaced, cancelled, expired, or whose address another account took meanwhile
email_changed the person, or the administrator by (user or admin), target_user_id, target_login, old_email, new_email; a change by the person adds revoked_sessions

A confirmation link that matches no request writes nothing. See Your profile.

Repository provider changes

Event Account Details
repository_provider_changed the person who saved the change old_provider, new_provider, old_repository, new_repository, secret_rotated; the row names the project and the integration (repository_integration and its id)

Changing an integration's provider gives it a new webhook URL and a new secret; the secret itself is never logged. Other edits of a repository integration are not audit events. See Repository integrations.

Currencies

Event Account Details
currency_toggled the administrator code, is_active
reporting_currency_changed the administrator, or the project manager scope (instance or project), project_key for a project, old, new; a project's null means it follows the instance

Saving the value that is already set writes nothing. See Currencies and the reporting currency.

Event Account Details
development_link_detached the person who detached it issue, item_type (commit or pull_request), item_id, item (the commit SHA, or #N / !N), repository; the row names the project and the issue
development_link_moved the person who moved it the same, plus target_issue and target_project_id

A commit or pull request detached from an issue, or moved to another, through the API. Commit messages and pull request titles are never logged. See Repository integrations.

Exchange rates

Event Account Details
exchange_rate_created the administrator rate_id, rate_date, base, quote, rate, source (manual)
exchange_rate_updated the administrator the same, plus changes: each changed field with old and new
exchange_rate_deleted the administrator the deleted rate: rate_id, rate_date, base, quote, rate, source
exchange_rates_imported the administrator rows, created, updated, unchanged, date_from, date_to
exchange_rates_fetch_toggled the administrator enabled, url
exchange_rate_settings_changed the administrator changes: pivot, max_age_days or fetch_url, each with old and new

A CSV preview, a refused import and saving unchanged values write nothing. The daily fetch itself is not an audit event; its last outcome is shown on the admin page. See Exchange rates.

AI model prices

Event Account Details
ai_model_price_created the administrator price_id, provider, model, currency, effective_from, effective_to, source (admin), is_active, prices (each set price per 1M tokens); closed_price_id when it ended the previous price of the model
ai_model_price_updated the administrator the same, plus changes: each changed field (effective_to, is_active, aliases, note) with old and new
ai_model_price_deleted the administrator the deleted price, as for ai_model_price_created
ai_model_prices_seeded the administrator, or none when a worker loaded them at start as_of (the bundled list's date), inserted, closed, history_inserted, aliases_updated, unchanged, skipped (counts)

Saving unchanged values and loading a bundled list that changes nothing write nothing. The installation's first load runs in the database migration and is not audited. See AI model prices.

AI subscriptions

Event Account Details
ai_subscription_created the administrator subscription_id, target_user_id, target_login (the person who holds it), provider, plan_key, plan_name, tool_slugs, amount, currency, billing_period, starts_on, ends_on
ai_subscription_updated the administrator the same, plus changes: each changed field with old and new; a changed note shows only as notes: {"changed": true}
ai_subscription_deleted the administrator the deleted subscription, as for ai_subscription_created
ai_subscriptions_self_service_toggled the administrator enabled
ai_subscriptions_changed the account whose key an agent used, over MCP (specivo_ai_subscriptions adding, updating, ending or deleting) tool: "ai_subscriptions", source: "mcp", op, subscription_ids, target_login (whose subscriptions), and for a change the changed fields

Only an administrator's change to somebody else's subscription is recorded as ai_subscription_*. People's changes to their own subscriptions, and an administrator's to their own, are not, except that every change over MCP is ai_subscriptions_changed, like every agent tool call. Notes are never copied into the log. See AI subscriptions.

AI usage

Event Who acted Details
ai_usage_logged_for_other the account that logged the usage entry_id, target_user_id, target_login (whose usage it is), source, tool, provider, model, usage_date, project_key, issue, reporter_api_key_id; when a reporter sends new totals for an entry it already logged for that person, status: "updated" and revision too (a re-send with nothing new writes nothing)
ai_usage_updated_by_other the person who changed somebody else's entry the same, plus changes: each changed field with old and new; a changed comment shows only as comments: {"changed": true}
ai_usage_excluded, ai_usage_included whoever excluded the entry from reports, or included it again the entry, as above
ai_usage_deleted whoever deleted the entry the entry, plus its tokens and cost_type
ai_usage_logged, ai_usage_changed the agent account, over MCP (specivo_log_ai_usage; specivo_ai_usage updating, excluding or including an entry) tool: "log_ai_usage" or "ai_usage", source: "mcp", and the entry_id; a logged entry adds target_login, tool, model, usage_date, project_key and issue, a change its op and the changed fields
ai_usage_costs_recomputed the administrator who started the recompute provider, models, from, to, entries (read), changed, and before / after totals per currency of the marginal cost and the API-equivalent value

Usage people log for themselves through the web or the REST API is not recorded here: they can always see their own entries. Over MCP, like every agent tool call, it is (ai_usage_logged). Comments are free text and never copied into the log, and no prompt, code or file name is ever stored with AI usage.

Personal data

The audit log holds personal data, readable by every instance administrator:

  • Client addresses (IP) and user agents of the people and agents it records.
  • Logins of the accounts that acted or were acted on.
  • Email addresses in email change events: the old and the new address, and the address a change was requested for.
  • Search text people and agents entered (up to 500 characters).

Keep the retention period no longer than you need, mention the log in your privacy notice, and include it when you answer a request for someone's data. Behind a reverse proxy, set TRUSTED_PROXIES so the log records the client's address rather than the proxy's.