Skip to content

Audit Log

NORA records registry operations in a structured audit log. Each entry is a JSON object written to a single line (JSONL format).

VariableDefaultDescription
NORA_AUDIT_LOGfileOutput mode: file, stdout, both, or off
config.toml
[audit]
mode = "file" # "file", "stdout", "both", "off"

Modes:

ModeBehavior
fileWrite to {storage_path}/audit.jsonl
stdoutWrite to stderr (12-factor compatible, works with log aggregators)
bothWrite to both file and stderr
offDisable audit logging

Each line is a JSON object with these fields:

FieldTypeDescription
tsstringISO 8601 timestamp (UTC)
actionstringWhat happened — see Action types
actorstringWho triggered it — see Actor values
artifactstringPackage, path or image reference; empty for pull, cache_hit and proxy_fetch
registrystringRegistry type: docker, npm, pypi, maven, raw, etc.; admin for token mints, the reindex scope for reindex, * for retention
detailstringAdditional context: blob/manifest for Docker, the curation rule and reason for curation_block, role and TTL for admin_token_mint, counts for retention-apply
{"ts":"2026-10-13T07:46:45.283Z","action":"push","actor":"ci","artifact":"team/a.txt","registry":"raw","detail":""}
{"ts":"2026-10-13T07:46:47.990Z","action":"pull","actor":"proxy","artifact":"","registry":"raw","detail":""}
{"ts":"2026-10-13T07:46:52.149Z","action":"push","actor":"api","artifact":"team/tiny:1.0","registry":"docker","detail":"manifest"}
{"ts":"2026-10-13T07:46:53.645Z","action":"delete","actor":"ci","artifact":"team/b.txt","registry":"raw","detail":""}
{"ts":"2026-10-13T07:47:03.543Z","action":"push","actor":"anonymous","artifact":"team/c.txt","registry":"raw","detail":""}
{"ts":"2026-10-13T08:00:00.000Z","action":"retention-apply","actor":"scheduler","artifact":"12 versions","registry":"*","detail":"keys=36 bytes_freed=104857600 duration=1.2s"}
ActionDescription
pushClient uploaded an artifact
pullCargo, pub or Raw download served from local storage
deleteClient deleted an artifact
overwriteExisting Raw artifact replaced
mountDocker cross-repository blob mount
cache_hitRequest served from the proxy cache without contacting upstream
proxy_fetchArtifact fetched from an upstream registry
curation_blockDownload refused by curation
reindexIndex rebuild requested through POST /api/v1/admin/reindex, or an RPM/Debian repository reindex
admin_token_mintAdmin created an API token for a user through POST /api/v1/admin/tokens (artifact is the user; the token itself is never logged)
retention-applyRetention run deleted versions (one summary entry per run)
ActorWhen
usernameAuthenticated user for push, delete, overwrite and RPM/Debian reindex in Cargo, Maven, npm, PyPI, Raw, RPM and Debian; also for curation_block, admin reindex and admin_token_mint. An API token records its owner’s username
anonymousThe same operations when the request carries no identity (authentication disabled or anonymous access)
apiDocker push, delete and mount — Docker writes do not record the username
proxypull, cache_hit and proxy_fetch
schedulerretention-apply from the built-in retention scheduler
cliretention-apply from nora retention-apply

When using file or both mode, the audit log is written to:

{NORA_STORAGE_PATH}/audit.jsonl

Default: data/audit.jsonl (relative to the working directory).

The file is created automatically. Parent directories are created if they do not exist.

services:
nora:
image: ghcr.io/getnora-io/nora:latest
environment:
NORA_AUDIT_LOG: both
NORA_STORAGE_PATH: /data
volumes:
- nora-data:/data

Then tail the log:

Terminal window
docker exec nora tail -f /data/audit.jsonl | jq .

With stdout mode, audit entries go to stderr alongside regular logs. Use your container orchestrator’s log driver to forward them:

Terminal window
# Filter audit entries from container logs
docker logs nora 2>&1 | grep '"action":' | jq .

For dedicated file-based forwarding (Filebeat, Promtail, Vector):

filebeat.yml
filebeat.inputs:
- type: log
paths:
- /data/audit.jsonl
json.keys_under_root: true
json.add_error_key: true
Terminal window
# All pushes today
grep '"action":"push"' /data/audit.jsonl | jq .
# Everything user "ci" pushed, overwrote or deleted
# (Docker writes are recorded with actor "api", not the username)
jq -c 'select(.actor == "ci")' /data/audit.jsonl
# Downloads refused by curation
jq -c 'select(.action == "curation_block")' /data/audit.jsonl
# Count operations per registry
jq -r '.registry' /data/audit.jsonl | sort | uniq -c | sort -rn