env-master

Auditing access to secrets: what belongs in the log

Short answer: a secrets access log is not a line in the general activity feed — it is a separate stream of events where every read of a value, by a person or a machine, is recorded with the key, the subject and the moment, but never the value itself. For the log to be trustworthy, entries are chained under a key that lives outside the database, so history cannot be rewritten after the fact without that key. In env-master the log consists of two streams (changes and reads), is verified with the audit:verify command, and once an hour the server writes a signed anchor of the chain head into a log that leaves the host.

An activity feed is not an audit

“User X changed a setting” and “service Y read DB_PASSWORD” are events of different nature. The first interests a manager, the second an investigator. Mixed into one feed, reads drown in configuration noise, and extracting “who touched the secrets this quarter” turns into manual filtering. That is why env-master keeps two streams — changes and reads — linked by one chain at the organization level. Every read of a secret, by a person in the dashboard or by a service at startup, is an event, not a side effect.

The log holds no values — a requirement, not a limitation

An entry carries the key name and an HMAC of the value. That is enough to see “the value changed” and “this read returned the same value as yesterday”, and not enough to learn the value itself. An audit log is by definition read by many people and kept for a long time — if it contained values, it would itself become a secrets store with worse properties than the real one.

Why the chain is keyed, not plain hashes

A chain of plain hashes can be verified by anyone — and therefore recomputed by anyone. Someone with write access to the database could delete events, rebuild the chain from the break and rewrite the head stored right there. So each link is computed under a key derived from the audit key, which lives in the service's environment, not in the database: without it the chain cannot be recomputed. The chain head is signed too — truncating the log by deleting the latest events does not work without the key either.

Anchors against restoring from a backup

One case remains that cannot be distinguished from inside the database: rolling back to a state that was once genuine, from an old copy. Against it the server writes an anchor — a signed snapshot of the head — into its log once an hour, and that log leaves the host. Feed the accumulated anchors to the verifier (audit:verify --anchors <file>), and it confirms that every recorded link is still in place — and that the log has not been wiped entirely. The limits are stated honestly: a rollback to a state newer than the last anchor is invisible by construction (the window is one hour), and anchors accumulated before they gained their own signature are flagged as unsigned — accepting the old form would mean leaving open the very door the signature was added to close.

A property you cannot verify is not a property

None of the above is a promise — it is a procedure you can run: audit:verify walks from the first event link by link and checks each one, noticing along the way what comparing hashes individually would miss: a fork, a truncated tail and events unreachable from the start of the chain. The full construction is described in the threat model, the short version on the security page.

Log retention differs by plan only in depth (from 7 days on Free to a year on Business — see pricing); the fact that every read is recorded and the chain is verifiable is the same on every plan. Auditing that switches on for money is auditing that gets switched off exactly when it is needed most.

← All articles