Audit Logging
The complete audit log reference for SikkerKey. Every vault action, its severity classification, and how to consume audit events via the dashboard, email alerts, and webhooks.
Every vault action in SikkerKey is recorded in an append-only audit log. Secret reads, authentication failures, single sign-on, permission changes, machine registrations, team invites, session management, integration activity, billing events, account lifecycle. Every entry records who did what, from where, when, and how sensitive the action was.
Entry Shape
| Field | Description |
|---|---|
action | The operation identifier (e.g. secret_read, auth_failure) |
severity | One of critical, high, medium, low, info |
actorKind | Who performed the action: user, machine, ai_agent, system, or external (an unauthenticated outside party, such as a failed SAML SSO sign-in). The dashboard renders the actor's name and an icon based on this field. |
userId | The vault owner's ID. Set on most entries to scope the row to the owner's audit log; not on its own an indicator that the user performed the action. |
machineId | The machine involved in the action. May be the actor (when actorKind = machine) or the target the user acted on (e.g. on machine_approve). |
aiAgentId | The AI agent involved in the action. AI agents are a separate identity class from machines and have their own ID. Set only when actorKind = ai_agent. |
secretId | The secret involved (if applicable) |
sourceIp | The IP address the request originated from. Background tasks use the sentinel string system; Stripe webhook events use stripe. |
detail | Human-readable description of what happened |
timestamp | Millisecond-precision Unix timestamp |
The actorKind field is the authoritative answer to "who did this." A machine_revoke row records both userId (the human who clicked revoke) and machineId (the machine they revoked), but actorKind = user makes clear which one was the actor. A temp_machine_create row also records both, with actorKind = machine because the bootstrap script (running as the machine) registered itself; the userId is just the owning vault for scoping.
The log is append-only. Entries are never modified or deleted by any operation. Retention is pruned on a schedule according to your plan.
Severity Levels
Severity is assigned automatically per action. The classification drives the dashboard's filter controls, email alert defaults, and webhook routing.
| Severity | Meaning |
|---|---|
critical | Active attack signal, account compromise, or irreversible destruction. Paging-worthy. |
high | Security-relevant event that may indicate an attack, or a trust boundary expansion. Review-worthy. |
medium | Legitimate access or trust boundary change. Worth noting. |
low | Routine administrative change. Recorded for completeness. |
info | Normal operation. The bulk of the log volume. |
Action Reference
Critical
| Action | When it fires |
|---|---|
auth_failure | A machine authentication attempt failed (missing headers, unknown machine, disabled machine, invalid signature, expired timestamp, replayed nonce, suspended owner) |
refresh_token_replay | A refresh token was used after it had already been rotated. SikkerKey treats this as detected theft: every active session is immediately revoked, and re-login is required on every device. |
webauthn_counter_anomaly | A passkey assertion presented a signature counter at or below the previous value. The canonical signal of a cloned authenticator. The sign-in is rejected and the credential involved is named in the entry. |
webauthn_recovery | An account was recovered using a passkey recovery code. Every passkey is removed, the require-passkey and passwordless flags are cleared, and every other session is revoked. Fires once per recovery, regardless of how many credentials were wiped. |
sensitive_action_lockout | A re-authentication gate hit its attempt cap (password change, 2FA disable, vault destruction, first-passkey registration, employee password change). SikkerKey treats this as an authenticated-session brute-force: the current session is revoked immediately, and further attempts on that gate are rejected for a 15-minute window. The detail names the gate. |
project_delete | A project and all its contents were deleted |
2fa_disable | Two-factor authentication was disabled on an account |
vault_destroyed | A vault was permanently destroyed |
account_revoked | An account was revoked for policy or security reasons |
ip_allowlist_disable | The account-wide IP allowlist was disabled |
canary_triggered | A canary secret was read by a granted machine. Signals an active compromise of that machine. |
project_freeze | A project was frozen in response to a canary trigger. One entry per frozen project. |
secret_destroyed | A bound secret was destroyed by a TTL trigger. Detail names the trigger source: system:ttl-time for time-based expiry (background pruner), system:ttl-reads for read-count exhaustion (inline post-fetch). |
High
| Action | When it fires |
|---|---|
ai_agent_register | A new AI agent registered against a one-time bootstrap token. New management-plane identity in the vault. |
ai_agent_revoke | An approved AI agent was revoked and removed. Cascades through scopes, allowlist, and name history. |
ai_agent_scopes_update | An AI agent's scope set was changed. Privilege boundary moved; review the diff in the detail field. |
machine_register | A new machine was registered via bootstrap |
machine_revoke | An approved machine was revoked and removed |
machine_enroll | A machine was registered via an enrollment token (multi-use provisioning) |
machine_provision | A machine identity was pre-provisioned from the dashboard |
machine_provision_download | A provisioned identity bundle was downloaded |
enrollment_token_revoke | An enrollment token was revoked before its expiry |
enrollment_token_denied | An enrollment attempt was denied (invalid token, machine count exceeded) |
project_machine_add | A machine was linked to a project, gaining access to that project's secrets |
project_unfreeze | A vault owner cleared a canary-triggered freeze on a project. |
secret_read_denied | A machine tried to read a secret it doesn't have access to |
secret_read_blocked | A machine passed the six-requirement gate but was denied by the bound access policy (time window, IP, rate cap, TTL, or co-sign). The detail names the failing axis. |
temp_machine_blocked | A temporary machine's signed request was blocked by one of its per-machine guardrails (IP allowlist, geo, or time window). Detail names the failing axis. A series of these is the canonical signal that an out-of-scope party has gotten hold of the identity. |
secret_rotate_denied | A machine tried to rotate a secret it doesn't have access to |
secret_delete | A secret was moved to trash |
secret_hard_delete | A secret was permanently deleted, bypassing trash |
secret_export | A bulk export of secrets was performed |
permission_grant | A machine was granted access to secrets in a project |
org_member_accept | A user accepted an organization invite and joined as a member |
org_member_template_change | An organization member's assigned template was changed, moving their capability set |
org_member_scope_change | An organization member's project scope was changed, moving the set of projects their project-scoped capabilities apply to |
org_member_remove | An organization member was removed from the vault (recorded on both the owner's and the removed member's logs) |
org_member_leave | An organization member voluntarily left the vault (recorded on both the leaving member's and the owner's logs) |
team_member_remove | Historical. No new events fire. A team member was removed from the vault (recorded on the owner's log). |
team_revocation | Historical. No new events fire. A team member was removed from the vault (recorded on the member's log). |
password_reset | A password was reset via the reset flow |
password_change | A password was changed by an authenticated user |
password_change_failed | A password change attempt failed |
password_set | A password was added to an account that previously had none (e.g. an OAuth-only or passkey-only account opting back into password sign-in) |
password_remove | A password was removed from an account, leaving the user to sign in with a passkey or OAuth provider only |
login_failed | A login attempt failed |
2fa_failed | A two-factor authentication step failed |
recovery_code_used | A 2FA recovery code was consumed |
webauthn_register | A passkey was registered to the account |
webauthn_revoke | A passkey was removed from the account |
webauthn_failed | A passkey ceremony failed (unknown credential, ownership mismatch, assertion verification rejected) |
webauthn_policy_update | The passkey policy changed (require-passkey-at-sign-in or passwordless-sign-in flags) |
oauth_link | An OAuth provider (GitHub or Google) was linked to the account, adding it as a sign-in path |
oauth_unlink | A linked OAuth provider was removed from the account |
sso_config_update | The organization's SAML SSO configuration was changed: identity provider entity ID, sign-in URL, signing certificate, default member template, or the enabled / enforce toggles. The signing certificate is the SSO trust anchor, so the entry records its fingerprint, and both the previous and new fingerprints when it changes. |
sso_domain_verify | An email domain was verified for the organization via DNS TXT record, turning on automatic member provisioning for that domain on first SSO sign-in. |
sso_login_failed | A SAML SSO sign-in was rejected. The detail names a sanitized reason category (invalid signature, audience mismatch, replay, expired assertion, unverified domain, and similar). Attributed to the external actor kind, since the attempt carried no authenticated identity. |
Medium
| Action | When it fires |
|---|---|
ai_agent_approve | A pending AI agent was approved and can now authenticate against the vault. |
ai_agent_deny | A pending AI agent was denied and removed before it could be approved. |
ai_agent_enable | A previously disabled AI agent was re-enabled. Its scopes and allowlist were preserved across the disable, so this re-grants the original capabilities. |
ai_agent_disable | An AI agent was disabled. Signed requests by the agent are refused with HTTP 403 until re-enabled. Scopes and allowlist preserved. |
ai_agent_allowlist_update | An AI agent's project allowlist was changed. Project-context routes are filtered to the new list. |
machine_approve | A pending machine was approved and can now authenticate |
machine_deny | A pending machine was denied |
machine_expire | An ephemeral machine reached its expiration time and was disabled |
enrollment_token_create | A new enrollment token was generated |
temp_machine_token_revoke | A temporary machine bootstrap token was manually revoked before use |
temp_machine_extend | A temporary machine's expiry was pushed further into the future. Capped at a rolling twelve months of remaining lifetime. |
temp_machine_extend_revert | The most recent un-reverted extension on a temporary machine was rolled back, restoring the prior expiry |
temp_machine_guardrails_update | A temporary machine's per-machine guardrails (IP allowlist, geo, or time window) were changed |
project_machine_remove | A machine was detached from a project |
org_member_invite | An organization invite was sent (also written to the invitee's log as a notification) |
org_member_invite_revoke | A pending organization invite was revoked, or an invited user declined the invite |
org_member_suspend | An organization member was suspended. Effective immediately on their next request. |
org_member_unsuspend | A suspended organization member was restored. |
org_template_create | A new organization template was created |
org_template_update | An organization template was edited (name, description, or capability set). Per-capability diff lives in the template-capability audit. |
org_template_archive | A template was archived. Requires no members assigned. |
org_template_unarchive | A previously archived template was restored. |
org_template_delete | A template was hard-deleted. Requires no members assigned. The capability-change audit rows survive. |
vault_context_switch | A member picked a vault from the post-login picker, setting their session's active-vault context. |
vault_converted_to_organization | A personal vault was converted to an organization. One-way. |
sso_domain_add | An email domain was added to the organization for SSO. The domain is unverified and has no effect until its DNS TXT record is confirmed. |
sso_domain_remove | An email domain was removed from the organization, ending SSO provisioning for accounts in that domain. |
team_permission_update | Historical. No new events fire. A team member's permissions on a project were changed. |
team_invite | Historical. No new events fire. An invite was sent to a user. |
team_invite_cancelled | Historical. No new events fire. The owner cancelled a pending invite. |
team_access_revoked | Historical. No new events fire. A team member's access was revoked. |
team_left | Historical. No new events fire. A team member voluntarily left a vault. |
team_project_remove | Historical. No new events fire. A team member was removed from a specific project (recorded on the owner's log). |
team_project_revocation | Historical. No new events fire. A team member was removed from a specific project (recorded on the member's log). |
password_reset_request | A password reset was requested |
session_revoke | A specific session was revoked |
session_revoke_all | All other sessions were revoked |
secret_restore | A previously trashed secret was restored |
secret_schedule_create | An automatic rotation schedule was created |
secret_schedule_update | A rotation schedule was modified |
secret_schedule_delete | A rotation schedule was removed |
ip_allowlist_add | An IP or CIDR was added to the allowlist |
ip_allowlist_remove | An IP or CIDR was removed from the allowlist |
ip_allowlist_enable | The IP allowlist was enabled |
access_policy_create | An access policy was created in a project |
access_policy_update | An access policy's fields were modified. Affects every secret bound to it on the next fetch. |
access_policy_delete | An access policy was removed. Only possible after every bound secret has been detached. |
secret_policy_bind | A secret was bound to an access policy |
secret_policy_unbind | A secret was detached from its access policy and reverts to unconstrained |
supabase_integration_connect | A Supabase integration was connected via personal access token |
sync_config_delete | A managed secret's sync configuration was removed |
agent_status_change | A managed secret agent's connectivity state changed |
canary_enable | A canary secret was armed (reads fire configured triggers). |
canary_disable | A canary secret was disarmed (reads are still audited but do not freeze). |
canary_config_update | A canary's trigger toggles (freeze-project, freeze-related) were changed. |
temporary_secret_failed | A temporary secret view attempt failed (wrong passphrase destroys the secret) |
payment_action_required | A payment requires additional authentication |
payment_failed | A payment attempt failed |
subscription_past_due | The subscription entered past-due state |
subscription_paused | The subscription was paused |
subscription_cancel | A subscription was cancelled |
Low
| Action | When it fires |
|---|---|
ai_agent_token_create | A one-time AI-agent bootstrap token was issued. The capability change comes when an agent redeems it (ai_agent_register), not at issuance. |
team_invite_declined | Historical. No new events fire. A user declined a vault invite. |
bootstrap_token_create | A bootstrap token was generated |
webauthn_rename | A passkey's friendly name was changed |
machine_purge | An expired ephemeral machine was permanently removed by the background cleaner |
enrollment_token_expire | An enrollment token reached its expiration time |
temp_machine_token_create | A temporary machine bootstrap token was issued |
temp_machine_create | A machine registered via a temporary bootstrap token. Begins in pending state and requires manual approval. |
temp_machine_purge | An expired temporary machine was permanently removed after the 30-day retention period |
subscription_initiated | A subscription was initiated |
subscription_activated | A subscription became active |
subscription_cancel_requested | A cancellation was requested |
supabase_integration_disconnected | A Supabase integration was disconnected |
temporary_secret_create | A one-time temporary secret was created |
webhook_create | A webhook endpoint was added |
webhook_update | A webhook endpoint was modified |
webhook_delete | A webhook endpoint was removed |
Info
| Action | When it fires |
|---|---|
ai_agent_rename | An AI agent was renamed. Recorded in the agent's name history. |
temp_machine_expired | A temporary machine reached its expiration time and was disabled |
secret_read | A machine successfully read a secret |
secret_create | A secret was created |
secret_update | A secret's value was replaced or rolled back |
secret_rotate | A secret was rotated |
secret_rename | A secret was renamed |
secret_note_update | A secret's note was edited |
project_create | A new project was created |
project_update | A project was renamed or its description changed |
machine_rename | A machine was renamed |
login_success | A user logged in successfully |
logout | A user logged out |
oauth_login | A user logged in via OAuth |
webauthn_login | A user signed in with a passkey, either as the only factor (passwordless mode) or as the second factor after a password |
sso_login | A user signed in to an organization vault via SAML SSO |
user_register | A new user account was created |
username_change | A user changed their account username. The detail records the previous and new username. |
2fa_enable | Two-factor authentication was enabled |
2fa_setup | A 2FA setup step was completed |
team_invite_accepted | Historical. No new events fire. A user accepted a vault invite. |
team_joined | Historical. No new events fire. A user joined a vault (their side). |
subscription_cancelled | A cancelled subscription reached its end-of-term |
sync_config_create | A managed secret's sync configuration was created |
sync_config_read | A managed secret agent fetched its sync configuration |
ci_template_render | A CI/CD bootstrap script was generated from the dashboard for an enrollment token. Detail names the target platform, token name, and the number of projects and secrets in scope. |
lease_created | A dynamic secret lease was issued |
lease_revoked | A dynamic secret lease was revoked |
temporary_secret_viewed | A temporary secret was viewed by the recipient |
webhook_test | A webhook test delivery was sent |
trash_cleanup | Expired trashed secrets were permanently deleted by the background pruner |
Real-Time Updates
When an audit entry is recorded, it is pushed to the vault owner's dashboard via Server-Sent Events. The audit page updates in place without polling. Team members see events relevant to projects they have access to.
Email Alerts
Vault owners can subscribe to email alerts per action from the Alerts page. When an action with an active alert fires, an email is dispatched asynchronously, the original request is never blocked. Alerts are gated by your plan's email alert feature.
Severity is included in the alert so your inbox rules can triage accordingly. Critical alerts are separated visually from routine ones.
Webhooks
Vault owners can register webhook endpoints that receive audit events as HTTP POST requests. Each webhook can subscribe to specific actions or whole categories. The body includes the full audit entry plus a signature header for origin verification.
See Webhooks for endpoint registration, payload shape, and signature verification.
Historical: Team Member Visibility
This section describes the legacy Teams feature, which has been replaced by Organizations. The team_* rows above remain in the table so audit entries written before the rollover stay readable. No new team_* events fire on current SikkerKey.
When a team member performed an action on a shared project, the audit entry was written under the vault owner's log with the team member's username referenced in the detail. Some actions also wrote a mirrored entry under the team member's own audit log so they could see their own activity history.
Examples of mirrored entries:
team_revocationon the member's log when the owner removed themteam_project_revocationon the member's log when the owner removed them from a single project- Secondary
permission_grant,project_machine_add, and similar entries on the acting team member's log when they performed these actions on shared projects
Organization Member Attribution
Actions performed by an organization member are written under the organization vault's audit log with the member's username as the actor. The actor_kind column resolves to user; the userId column carries the member's user id (not the vault owner's). When the action involves a cross-actor notification (e.g. a member accepts an invite, which writes a mirror row to the owner's log), the mirror row carries actor_kind = system so the owner is not labelled as the actor of an action they didn't take. The detail string names the acting member in those cases.
AI Agent Attribution
Actions performed by an AI agent carry the agent's id in the aiAgentId field. The detail string also names the agent (e.g. "AI agent 'codex-prod' rotated secret 'stripe-key' in 'production'") so a reader can identify the actor without joining tables.
In the audit table, AI-agent-attributed entries show the agent's name in the actor column. AI agents and machines are physically distinct identity classes, and an entry never carries both machineId and aiAgentId.
Because every AI agent action signs through the management surface, there is no offline write path: bypassing the audit log requires bypassing the entire request path. See the MCP Security Model for the contract.
Dashboard Filtering
The Audit page supports:
| Filter | Effect |
|---|---|
| Search | Full-text over the detail field, debounced |
| Action | Restrict to one or more action types |
| Severity | Restrict to one or more severity levels |
| Source IP | Exact match on source IP |
| Time range | Last hour, 24 hours, 7 days, 30 days |
Filters combine with AND. Server-side pagination at 50 entries per page.
What the Audit Log Does Not Contain
- Decrypted secret values. Reads record that a secret was read, never the plaintext.
- Passphrases. They are never logged.
- Private keys. Machine private keys are never transmitted to SikkerKey in the first place.
- Session cookies or JWT contents.
- Billing card details. Stripe object IDs appear; card numbers do not.
Retention
Audit log retention is plan-based and pruned automatically by a background task. When a retention window passes, older entries are permanently deleted. Retention is not uniform across severity levels: high-severity entries are kept for twice your plan's window, and critical entries are never pruned. Critical covers authentication failures, disabling two-factor authentication, account revocation, disabling the IP allowlist, project deletion, and vault destruction, so the record of a destructive or security-relevant event outlives routine activity.
Destroying a vault is the one exception to "never pruned." When a vault is destroyed, its audit log is kept for about 30 days and then erased in full, every severity included, along with the rest of the vault's data, by which point the encrypted backups that held it have also expired.