The audit log
Thirty-three action types are recorded per project, each with an actor, a target, and a timestamp.
The question it exists for
Section titled “The question it exists for”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.
What’s recorded
Section titled “What’s recorded”| 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.
What it never contains
Section titled “What it never contains”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.
Who can read it
Section titled “Who can read it”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.
Reliability
Section titled “Reliability”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.
An honest limitation
Section titled “An honest limitation”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.