Skip to content
Get early access

The audit log

Thirty-three action types are recorded per project, each with an actor, a target, and a timestamp.

Who pulled production before the leak.

That is the single most valuable question a secrets manager can answer after an incident, and answering it requires recording reads — not just changes. Most audit logs record writes, because writes are easy and reads are noisy. Reads are the ones you need.

So every download of an environment’s payload is a row: payload.read, with a name and a time on it.

Area Actions
Account user.register, user.login, user.login_failed, user.refresh_reuse, user.keys_rotated
Recovery recovery.start, recovery.complete, recovery.request, recovery.rotate, recovery.fulfill
Projects project.create, project.rename, project.delete
Membership member.invite, invite.accepted, invite.revoked, member.confirm, member.role_change, member.remove
Environments env.create, env.rename, env.delete, env.reap, env.reorder, env.rotate
Payloads payload.read, payload.write
Grants grant.create, grant.update, grant.revoke
Sharing share.create, share.open, share.revoke

Some of these describe capabilities that aren’t built yet — the share.* family covers link sharing, which is designed but unimplemented. The taxonomy is fixed ahead of the features so that action names never have to be reinterpreted later.

No secret values. No keys, wrapped or otherwise. No note text. No plaintext of any kind.

An audit log is a security feature that is also, structurally, a log — and a log that quotes the thing it is protecting is a liability. Metadata only: who, what kind of action, which target, when.

Project admins and owners. It is scoped per project.

Account-level events — a password change, a recovery — are recorded but are not surfaced by the project-scoped read, since they don’t belong to any one project. An account-level view is a gap rather than a decision.

Most audit writes are fail-closed: if the row can’t be written, the operation it describes is rolled back. A key rotation that isn’t recorded doesn’t happen. That is the right default for anything touching credentials, because an attacker who can suppress the log gets to act invisibly.

Login and failed-login rows are deliberately best-effort instead. Making them fail-closed created a worse problem: during an audit outage, a wrong password on a real account would return a server error while an unknown email returned a normal rejection — which tells an attacker which addresses have accounts. Best-effort closes that oracle. The trade is stated rather than hidden.

Pagination uses a database identity column, which is monotonic across all projects rather than per project. So an admin comparing consecutive IDs from their own project’s log can infer a rough upper bound on how much total audit volume occurred in between.

It’s admin-only, content-free, attribution-free, and noisy. It’s also real, so it’s written down in the threat model rather than left for someone to discover.