Read and retain the audit log
By the end of this guide, you can answer “who did what on this VTA, and when” from the audit log, and set how long those records are kept.
A VTA records every privileged operation as it happens: the actor’s DID, the action, the context, and the outcome. This guide covers querying that record with pnm audit and setting its retention period. For what the VTA considers a privileged operation in the first place, see Request model.
Without a way to read it, the audit trail only helps after someone has already exported it somewhere else. Reading it directly is what turns questions like “which DID released that secret last Tuesday” into a single query, and it is usually the first thing asked after a credential is suspected of leaking.
Use this guide when:
- You need to establish what a particular DID did, and when.
- A credential may have leaked and you want to see what was done with it.
- An operation failed and you want the recorded outcome rather than a client-side guess.
- You need to set or confirm how long this VTA keeps audit records.
Prerequisites
A running VTA. See Quickstart.
pnminstalled and connected to the VTA.cargo install pnm-cli@0.16.4 --locked --registry crates-ioAdmin access. A context-scoped admin can read their own contexts; an unrestricted admin can read across all of them.
Read the log
pnm --vta <vta-slug> audit list --context my-app--context is required unless you are an unrestricted admin, which is the same scoping that applies everywhere else on a VTA. An empty result reports No audit log entries found.
Narrow the query
Each filter is an exact match, and they combine:
# Everything one DID did in a context
pnm --vta <vta-slug> audit list --context my-app --actor did:key:z6Mk...
# One kind of action, in a time window
pnm --vta <vta-slug> audit list --context my-app \
--action key.create \
--from 2026-09-01T00:00:00Z \
--to 2026-10-01T00:00:00Z
# Only the operations that were refused
pnm --vta <vta-slug> audit list --context my-app --outcome denied| Filter | What it takes |
|---|---|
--from / --to | RFC 3339 timestamps. --from is inclusive, --to is exclusive. |
--action | An exact action name, such as auth.challenge or key.create. |
--actor | The DID that performed the operation. |
--outcome | An exact outcome, such as success or denied. |
--context | The context to read. Required unless you are an unrestricted admin. |
Filtering on --outcome denied is the fastest way to find access that was attempted and refused, which is what you usually want after a credential is suspected of leaking.
Page through a large result
The default page size is 50 and the maximum is 200. When more entries match than fit on a page, the command prints a continuation cursor:
pnm --vta <vta-slug> audit list --context my-app --limit 200
pnm --vta <vta-slug> audit list --context my-app --limit 200 --cursor <cursor-from-previous-page>Pass the cursor back verbatim and keep every other filter identical. Changing a filter mid-sweep invalidates the cursor, and the results after that point no longer answer the question you started with.
Set how long entries are kept
# See the current period
pnm --vta <vta-slug> audit retention get
# Set it
pnm --vta <vta-slug> audit retention set 90 Audit Retention
Retention period: 90 daysRetention accepts 1 to 365 days. Choose it against whatever obligation you actually have to meet, and remember that shortening the period discards entries older than the new window.
Audit entries are excluded from pnm backup export by default, because they can be large. Add --include-audit when you export if the backup is meant to preserve them. See Back up and restore VTA state.
If you need records to outlive this VTA’s retention period, export them on a schedule and keep them in whatever system already holds your long-term logs.
Confirm
Test 1: a known action appears in the log
Mint a key, then look for it:
pnm --vta <vta-slug> keys create --key-type ed25519 --context my-app --label audit-check
pnm --vta <vta-slug> audit list --context my-app --action key.createThe entry for the key you just minted appears, naming your DID as the actor.
Test 2: a refused operation is recorded as denied
pnm --vta <vta-slug> audit list --context my-app --outcome deniedOperations the VTA refused appear here rather than being silently dropped. An empty result means nothing has been denied in that context, which is itself worth knowing.
Test 3: the retention period is what you set
pnm --vta <vta-slug> audit retention getThe period matches the value from “Set how long entries are kept”.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
audit list refuses without a context | --context is required for anyone who is not an unrestricted admin. | Pass --context <id>, or run as a super-admin to read across contexts. |
| A time filter returns nothing | --from and --to need RFC 3339 timestamps, and --to is exclusive. | Use the full form, 2026-09-01T00:00:00Z, and set --to past the last moment you want included. |
| Paging returns unexpected entries partway through | A filter changed between pages, which invalidates the cursor. | Restart the sweep, keeping every filter identical and passing the cursor back verbatim. |
| An action you expected is missing | The action name is an exact match, or the entry predates the retention window. | Confirm the exact action name from an unfiltered listing, then check the retention period. |
| Audit entries missing after a restore | The backup was taken without --include-audit. | Re-export with --include-audit for future backups. Entries not captured cannot be recovered. |
Next steps
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.