Audit log

IVRE keeps an append-only audit log of the security-relevant actions performed through its web layer: data uploads, admin actions, and queries large enough to be truncated. It is a security control first and a debugging aid second – the storage layer is fail-loud, so an event that cannot be written aborts the request rather than silently dropping the record.

The audit purpose (db.audit, ivre.db.DBAudit) is independent from the data purposes (nmap / view / passive / flow): it can point at a different, write-restricted backend, and a deployment that does not opt into it simply has db.audit is None with no events recorded.

Configuration

The backend is selected by the DB_AUDIT option in ivre.conf. When left unset it defaults to the value of DB, so operators get a working db.audit out of the box:

# Same store as everything else (the default): leave
# DB_AUDIT unset and it follows DB.

# Or send the audit stream to a dedicated, write-restricted
# store (typical for compliance setups):
DB_AUDIT = "postgresql://ivre:ivre@db.example/ivre_audit"

The audit purpose is supported on MongoDB, PostgreSQL and DuckDB. Provision (or re-provision) its collection / table and indexes with:

$ ivre auditcli --init            # drop + recreate (destroys events)
$ ivre auditcli --ensure-indexes  # create missing indexes, keep data

--ensure-indexes is idempotent and never drops rows; run it after upgrading IVRE on an existing deployment.

Event types

Three categories are recorded (the closed set in ivre.db.DBAudit.EVENT_TYPES):

upload

A successful write against POST /scans, POST /view, POST /flows or POST /flows/cleanup – data ingested through the web API, plus the flows-cleanup maintenance write.

admin_action

A mutating request under /auth/admin/* (creating or updating a user, revoking another user’s API key, …).

oversize_query

A record-listing request whose underlying result count exceeded WEB_MAXRESULTS. The event is recorded before the truncated response is streamed, so the trail captures queries that saw only part of the matching set.

Each event carries the actor (user e-mail, API-key hash and remote address, any of which may be empty for anonymous traffic), the targeted resource (route and method), an outcome (typically the HTTP status), a free-form details object whose shape depends on the event type, and a UTC created_at timestamp.

The route values above (and the resource.route field stored on each event) are the application paths the handlers register, matching the Web API reference. At runtime they are served under the deployment’s mount prefix – /cgi/ for ivre httpd and the bundled NGINX example, so /scans is reached at /cgi/scans and /audit/ at /cgi/audit/.

Events are produced automatically by the per-route hook in ivre.web.audit_hook; application code does not call the storage layer directly.

Reading the log

Command line

ivre auditcli is the operator-side reader. It runs locally against the configured backend with full privilege – the assumption being that shell access to the IVRE host already grants access to the raw collection – so it applies no per-user gating, unlike the web API below.

Actions (mutually exclusive; --query is the default):

--query

List matching events as a JSON array, newest first. --limit (default 100) and --skip paginate.

--count

Print the number of matching events.

--get EVENT_ID

Fetch a single event by id (any textual UUID form), or null when none matches.

--purge-older-than DURATION

Delete every event older than the cutoff and print the number removed (see Retention).

--init / --ensure-indexes

Collection / index lifecycle (see Configuration).

Filters (apply to --query and --count):

--event-type {upload,admin_action,oversize_query}

Narrow to one category.

--user-email EMAIL

Narrow to one user’s events.

--since WHEN / --until WHEN

Bound created_at. WHEN accepts duration shorthand relative to now (30d, 2h, 90m, …), a Unix timestamp, or an ISO-8601 datetime (naive values are read as UTC). --since is inclusive, --until exclusive.

The duration shorthand matches the timeago: syntax of the web query parser, so the same expression works on both sides.

Examples:

# Everything alice did in the last week:
$ ivre auditcli --user-email alice@example.org --since 7d

# How many oversize queries in the last 30 days:
$ ivre auditcli --count --event-type oversize_query --since 30d

# One event, pretty-printed:
$ ivre auditcli --get 3f1e8c1a... --indent 2

Web API

The same events are exposed read-only under /audit/*. These routes are admin-or-self gated: an authenticated user reads their own trail, an admin reads everyone’s, and an anonymous caller gets 401. A non-admin who asks for another user’s events with user_email= gets 403. When no audit backend is configured the routes return 501.

The full request / response reference (query parameters, status codes, response shapes) is generated from the route docstrings on the Web API page; see the List audit events, Count audit events and Read a single audit event endpoints. Authentication uses the standard web-layer session / API-key flow (see Web Authentication).

No MCP surface

The audit log is deliberately not exposed through the ivre mcp-server. It is a security control, and giving an LLM-driven agent a read path into the log would broaden the attack surface of the log itself (and risk leaking one user’s actions to another through a tool the gate does not cover). Programmatic consumers use ivre auditcli or the /audit/* routes, both of which apply the appropriate trust boundary.

Retention

Retention is operator-driven. IVRE ships no automatic expiry: there is no TTL index and no WEB_AUDIT_TTL_SECONDS (or equivalent) configuration knob. An audit log that silently sheds its oldest entries is a poor security control – the deletion policy should be an explicit, auditable operator decision, not a hidden default.

The single retention verb is:

$ ivre auditcli --purge-older-than 180d

which deletes every event whose created_at is older than the cutoff and prints the number removed. DURATION accepts the same shorthand as --since (180d, 2y, or a bare integer count of seconds).

Operators who want periodic pruning schedule this command themselves – for example a daily cron entry or a systemd timer:

# /etc/cron.d/ivre-audit-retention
17 3 * * *  ivre  ivre auditcli --purge-older-than 365d

Pointing DB_AUDIT at a dedicated, write-restricted store (for example a PostgreSQL instance with append-only controls) composes with this: the application only ever appends and runs the explicit purge, while the database enforces the WORM-style guarantees a compliance regime may require.