Audit Logging
Track every operation with tamper-evident, append-only audit logs.
Nefia records every command execution, file operation, and session event in an append-only audit log. Log entries are chained using SHA-256 hashes and signed with HMAC-SHA256, making any tampering detectable through verification.
What Gets Logged
| Event Type | Description |
|---|---|
| Session & Execution | |
session_open | Session opened with a target host |
session_close | Session closed |
exec | Command execution on a target host |
exec_denied | Operation blocked by policy engine |
facts | Host facts gathered |
| File System Operations | |
fs_read | File read |
fs_write | File write |
fs_patch | File patch (partial modification) |
fs_list | Directory listing |
fs_stat | File/directory stat |
fs_stream_read | Streaming file read (download) |
fs_stream_write | Streaming file write (upload) |
fs_symlink | Symbolic link created |
fs_readlink | Symbolic link target read |
fs_archive_create | Archive (tar/zip) created on target |
fs_archive_extract | Archive extracted on target |
fs_diff | File diff between hosts or snapshots |
| Playbook | |
playbook_start | Playbook execution started |
playbook_complete | Playbook execution completed |
| Configuration & Host Management | |
config_change | Configuration saved or updated |
host_revoke | One host's VPN access revoked |
host_revoke_all | Emergency revocation of all hosts |
| VPN Operations | |
vpn.peer_reconcile | VPN peer list reconciled |
vpn.invite | VPN invite token generated |
vpn.invite_revoke | VPN invite token revoked |
vpn.key_rotate | VPN WireGuard key pair rotated |
| JIT Access | |
jit.request | JIT access request created |
jit.approve | JIT access request approved |
jit.deny | JIT access request denied |
jit.revoke | JIT access grant revoked |
| Queue | |
queue.retry | Queued job retried |
| SSH CA | |
sshca.sign_user | SSH user certificate signed |
sshca.sign_host | SSH host certificate signed |
sshca.trust_known_hosts | SSH CA trust entries added to known_hosts |
| mTLS | |
mtls.issue_client | mTLS client certificate issued |
mtls.revoke | mTLS client certificate revoked |
| Team Management | |
team.create | Team created |
team.invite.create | Team invitation created |
team.member.join | Member joined team |
team.member.leave | Member left team |
team.switch | Active team switched |
| System Query (read-only) | |
sys_disk | Disk usage queried |
sys_env | Environment variables listed |
sys_network | Network interfaces listed |
sys_group_list | OS groups listed |
sys_service_show | Service details queried |
sys_service_logs | Service logs retrieved |
sys_cron_list | Cron jobs listed |
sys_mount_list | Mount points listed |
sys_sysctl_list | Kernel parameters listed |
sys_hostname | Hostname queried |
sys_time | System time queried |
sys_update_check | Available updates checked |
sys_logs_search | System logs searched |
sys_package_list | Installed packages listed |
net_ping | ICMP ping executed |
net_traceroute | Traceroute executed |
net_dns | DNS lookup performed |
net_connections | Active network connections listed |
net_listen | Listening ports listed |
firewall_list | Firewall rules listed |
firewall_status | Firewall status queried |
container_list | Containers listed |
container_inspect | Container details inspected |
| System Mutation | |
power_reboot | Host rebooted |
power_shutdown | Host shut down |
sys_package_install | Package installed |
sys_package_remove | Package removed |
sys_user_create | OS user created |
sys_user_modify | OS user modified |
sys_user_delete | OS user deleted |
sys_kill | Process killed |
sys_cron_create | Cron job created |
sys_cron_delete | Cron job deleted |
sys_service_enable | Service enabled |
sys_service_disable | Service disabled |
sys_service_reload | Service reloaded |
sys_hostname_set | Hostname changed |
sys_time_set | System time or timezone changed |
sys_update_apply | System updates applied |
sys_sysctl_set | Kernel parameter changed |
sys_mount | Filesystem mounted |
sys_umount | Filesystem unmounted |
firewall_add | Firewall rule added |
firewall_remove | Firewall rule removed |
container_logs | Container logs retrieved |
container_start | Container started |
container_stop | Container stopped |
container_restart | Container restarted |
| Composite Operations | |
run | One-shot command execution (composite) |
investigate | Comprehensive host investigation |
file_deploy | File pushed and verified |
service_deploy | Atomic service deployment |
config_apply | Atomic configuration update |
system_baseline | System baseline drift detection |
system_health | Comprehensive system health check |
Log Format
Audit logs are stored as JSONL (JSON Lines) files, date-partitioned with a new file per calendar day. The default directory follows the platform config path:
| Platform | Audit log directory |
|---|---|
| macOS | ~/Library/Application Support/nefia/audit/ |
| Linux | ~/.config/nefia/audit/ |
| Windows | %AppData%\nefia\audit\ |
For example, 2026-02-24.jsonl within that directory. Each entry contains these fields:
| Field | Type | Description |
|---|---|---|
seq | integer | Monotonically increasing sequence number |
prev_hash | string | hash value of the previous entry (chain link) |
hash | string | SHA-256 hash of the current entry |
hash_version | integer | Hash computation algorithm version (0-3) |
hmac | string | HMAC-SHA256 signature (for tamper detection) |
ts | string | ISO 8601 timestamp (UTC) |
host_id | string | Target host identifier |
op_kind | string | Event type (exec, fs_read, etc.) |
job_id | string | Unique job identifier for correlation |
session_id | string | Session identifier (if within a session) |
user | string | Authenticated operator identity |
agent_id | string | Agent identifier (if agent-initiated) |
request | object | Operation parameters (command, path, etc.) |
result | object | Outcome (exit code, bytes transferred, etc.) |
team_id | string | Team identifier (if team-scoped) |
actor_role | string | Actor's RBAC role (if team-scoped) |
artifacts | array | Related artifact paths (if any) |
source | string | Operation source: "cli" (human operator), "mcp" (AI agent via MCP), or "tui" (TUI dashboard). Present when hash_version >= 4. |
Example Entry
{
"seq": 142,
"prev_hash": "a1b2c3d4e5f6...previous entry hash...",
"hash": "f7e8d9c0b1a2...current entry hash...",
"hash_version": 3,
"hmac": "e3b0c44298fc1c149afbf4c8996fb924...",
"ts": "2026-02-24T09:15:32Z",
"host_id": "web-01",
"op_kind": "exec",
"job_id": "j-a8f3bc91",
"user": "operator@example.com",
"team_id": "team-abc123",
"actor_role": "admin",
"request": { "command": "systemctl restart nginx", "timeout": "30s" },
"result": { "exit_code": 0, "duration_ms": 1240 }
}Hash Chain Integrity
Audit log tamper detection consists of two layers: a SHA-256 hash chain and HMAC-SHA256 signatures.
SHA-256 Hash Chain
Each entry's hash is computed from the entry's content and the previous entry's hash using SHA-256. Tampering with any entry invalidates the hashes of all subsequent entries:
Entry 1: hash = SHA-256(seq=1 | prev_hash="" | fields...)
Entry 2: hash = SHA-256(seq=2 | prev_hash=entry_1.hash | fields...)
Entry 3: hash = SHA-256(seq=3 | prev_hash=entry_2.hash | fields...)HMAC-SHA256 Signatures
A hash chain alone allows an attacker to conceal tampering by recomputing the entire chain. To prevent this, each entry's hash is signed with an HMAC-SHA256 signature (the hmac field). The HMAC key is stored outside the audit log directory, under the parent configuration directory (<config_dir>/secrets/audit_hmac.key), so an attacker without the key cannot produce valid HMAC values even if they recompute the chain.
Hash Version
The fields included in the hash computation vary by version:
| Version | Fields Included | Notes |
|---|---|---|
| 0 | Core fields only (seq, prev_hash, ts, op_kind, host_id, job_id, session_id, user) | Legacy format |
| 1 | V0 + request, result | Base default (auto-promoted to V2 when HMAC key is present) |
| 2 | Same hash as V1 + HMAC signature | Effective default — HMAC key is auto-generated on first run |
| 3 | V1 + team_id, actor_role | Used with team features |
| 4 | V3 + source field | Auto-selected when an audit record includes a source value. Distinguishes AI agent operations from human operations. |
Integrity Protection on Write Failure
If a file write (write) or sync (sync) fails, the seq and prev_hash are rolled back to preserve hash chain consistency. The failed entry is not incorporated into the chain, ensuring that subsequent entries link correctly.
CLI Commands
View Recent Entries
nefia audit tail -n 200SEQ TIME HOST OP JOB_ID USER 138 2026-02-24T08:55:12Z web-01 exec_denied j-c4a28f63 operator@example.com 140 2026-02-24T09:12:01Z web-01 fs_read j-3ef19ab2 operator@example.com 141 2026-02-24T09:14:58Z web-02 fs_write j-7cd2e4f0 operator@example.com 142 2026-02-24T09:15:32Z web-01 exec j-a8f3bc91 operator@example.com
Show a Specific Job
nefia audit show --job j-a8f3bc91Verify Hash Chain
Verifies the hash chain integrity of a single day's audit log file. Defaults to today's date when no argument is provided.
nefia audit verify # verify today's log
nefia audit verify 2026-02-24 # verify a specific dateOK: 142 records verified for 2026-02-24 (hash chain + HMAC).
Performance and Resource Management
File Handle Reuse
Audit log writes are optimized by reusing file handles on a per-day basis. Only two system calls — write + sync — are needed per record (no open/close required).
Record write flow:
1. ensureFile(date) — skipped if the existing handle's date matches (O(1))
2. write(data) — direct write without buffering
3. sync() — immediate flush to disk (prevents data loss)Automatic File Rotation on Date Change
When the date changes (UTC-based), ensureFile() detects it, closes the previous day's file handle, and automatically opens a new file for the current day. No explicit rotation operation is required.
Resource Cleanup via Close()
When App.Close() is called, Audit.Close() is executed, performing the following:
- Closes the current file handle
- Waits for any running cleanup goroutines to complete
This ensures no file handle leaks occur on application shutdown.
Configuration
Configure audit logging in nefia.yaml:
audit:
enabled: true
required: false # when true, operations fail if audit write fails
dir: ~/.config/nefia/audit
retention_days: 90
syslog_addr: "localhost:514"
syslog_proto: "udp"| Key | Default | Description |
|---|---|---|
enabled | true | Enable or disable audit logging |
required | false | When true, audit write failures are treated as fatal errors — operations are blocked if audit logging fails. Default behavior is warn-and-continue. |
dir | Platform config dir + /audit | Directory for log files |
retention_days | 90 | Automatically delete logs older than this (0 = never) |
syslog_addr | "" | Syslog server address (e.g., localhost:514). Empty disables forwarding. |
syslog_proto | "udp" | Syslog protocol: udp or tcp |
When syslog forwarding is enabled, events are sent in real time alongside the local JSONL write. The local log is always written regardless of syslog availability, ensuring the hash chain remains intact.
Verification Workflow
For compliance or incident investigation, verify integrity first, then filter and inspect:
# Step 1: Verify the hash chain for today (or a specific date)
nefia audit verify
nefia audit verify 2026-02-24
# Step 2: View recent entries
nefia audit tail -n 100
# Step 3: Inspect specific jobs
nefia audit show --job j-a8f3bc91Related
Understand Nefia's defense-in-depth security architecture.
Define the rules that generate exec_denied audit events.