Audit trail
Every answer, and every refusal.
Most access logs record what worked. This one records what did not, with equal care, because a pattern of refusals is the more interesting signal – somebody probing at the edge of their access looks exactly like nothing at all in a log that only writes successes.
| Time | Person | Tool | Outcome | Reason |
|---|---|---|---|---|
| 09:14:02 | a.morgan | describe_model | allow | – |
| 09:14:38 | a.morgan | read_slice | allow | – |
| 09:15:11 | a.morgan | crm_read_all | deny | skill_not_enabled |
| 09:15:11 | t.walsh | docs_search | allow | – |
| 09:16:40 | t.walsh | read_slice | deny | no_grant_on_project |
| 09:22:03 | – | – | deny | credential_rejected |
The last row has no person on it on purpose. A credential rejected before it reached any permission check is still recorded, so somebody guessing at your connector is visible rather than silent.
The reason codes
Seventeen names, and a test that freezes them.
A refusal reason is a stable name, not a sentence someone rephrased. There are seventeen of them, and a test fails the build if one changes – because this is data somebody will read three years from now, and it has to still mean the same thing then.
Why not a message
A human-readable message is written for whoever is debugging that week. It gets reworded, it gets translated, and every rewording silently breaks the report somebody built on top of it.
Why a test rather than a convention
Conventions are followed until a deadline. The frozen set means renaming a code is a deliberate, visible act with a failing build attached, rather than a tidy-up in a pull request nobody reads closely.
What you do with them
Group by code. A rise in one specific refusal is a person hitting a boundary repeatedly, and it usually means a grant is wrong rather than that somebody is malicious.
The record
What a row contains, and what it deliberately does not.
On every row
- When – to the second.
- Who – the verified person, by their own identity rather than a shared credential.
- Which environment – never inferred, always named.
- Which tool was called.
- The outcome – allowed or refused.
- The reason – one of the seventeen names, on every refusal.
Never on a row
- The figures that came back. The record says a read happened, not what it returned.
- Document contents. Same reason.
- Anything about another organization. Not a name, not an identifier, not a slug.
- Free-text somebody can shape. A log line an attacker can write into is a log line you cannot trust.
The absence of figures is a deliberate trade. It means the audit trail cannot tell you what a person saw, only what they were permitted to ask for. Recording the values would make the log a second copy of your data, with all of the exposure and none of the controls.
Access to the record
It is yours, and reading it is itself governed.
| Question | Answer |
|---|---|
| Who can read it | Your own administrators, through the console, scoped to your organization. There is no cross-organization view, including for us. |
| Can you export it | Yes. It is your record and it leaves in a form you can put in front of an auditor. |
| Can you edit it | No. Neither can we, through the console. |
| Does it survive a revoked grant | Yes. Removing somebody's access does not remove the history of what they reached, which is the point. |
One honest caveat about permanence. The console offers no way to alter a row, and no feature exists to do it. That is an application-level guarantee, not a cryptographic one: it is a database, and a database administrator with direct access is a different threat model. If write-once storage is a requirement in your review, say so early rather than assuming it.
See what a month of it looks like.
The most useful thing in an evaluation is usually not the demo. It is a fortnight of real rows from your own environment, and the conversations that start when somebody reads them.