Audit log
/audit/ (needs audit_log.manage) records every request, including
plain GET/view traffic and not just state-changing ones, plus every login
attempt. Each entry captures the actor (user, username, and role at the
time), action, HTTP method, path, status code, related engagement/object,
client IP, query string, referer, user agent, and request duration.
Only HEAD/OPTIONS requests and a short list of specifically-excluded
views (the live report preview endpoint, the health check, and the CSP
report endpoint) are skipped. Everything else, including plain page loads,
is logged. Two sensitive URLs (password_reset_confirm,
account_setup_confirm) have their path redacted in the log, since the URL
itself contains a single-use token.
Browsing and filtering
Section titled “Browsing and filtering”The Audit Log page is filterable by free-text search (matches actor username, action, path, object reference, IP, user agent, or referer), HTTP method, actor, status-code bucket (2xx/3xx/4xx/5xx), and a from/to date range, with a configurable page size (25/50/100/250).
Retention
Section titled “Retention”AUDIT_LOG_RETENTION_DAYS (default 2190, which is 6 years and matches
HIPAA’s general documentation-retention rule; see config/settings/base.py’s
comment for how that compares to PCI DSS/NIST/CIS) controls how far back
entries are kept. Override it in .env per your own compliance obligations.
Purging is never automatic, since nothing in RedScribe runs on a schedule of its own, so wire it into your host’s own cron for a self-maintaining instance:
0 3 * * * cd /path/to/redscribe && docker compose exec -T web python manage.py purge_audit_logAdd --dry-run to preview the delete count first, or --days N to purge
against a one-off window instead of the configured default. A Superadmin can
also trigger the same purge on demand from the Purge entries older than N
days button on the Audit Log page. It’s the same underlying logic either
way, and the purge action itself is recorded in the audit trail like any
other state-changing request.
Clearing everything
Section titled “Clearing everything”Separate from the age-based purge above, the Audit Log page also offers
Clear all logs, an unconditional wipe of every audit-log and
login-attempt row, regardless of age. Because this is destructive and
irreversible, confirming it requires typing the exact phrase DELETE ALL LOGS into a confirmation field first; there’s no equivalent one-step CLI
flag for it.
Tamper-evidence
Section titled “Tamper-evidence”Every entry’s entry_hash is a keyed hash (HMAC-SHA256, keyed with the
instance’s Django secret key) computed over its own fields, chained onto
the previous entry’s hash. Changing a stored row anywhere in the chain,
including through direct database access rather than the app, invalidates
every hash after it. A brand-new instance’s very first entry chains from a
genesis hash of 64 zeros.
Check the whole chain at any time with:
docker compose exec web python manage.py verify_audit_logThe oldest surviving row can’t be checked against a prior link. Either it’s
genuinely the first entry ever, or everything before it was legitimately
removed by purge_audit_log. The command reports that row as an anchor,
not a failure, and flags the first row after it (if any) whose recomputed
hash doesn’t match what’s stored.