Log Format Reference
Complete specification of shellfirm's JSON-lines audit log format
shellfirm writes audit events as JSON-lines: one JSON object per line, appended to ~/.shellfirm/audit.log. This format is easy to parse, stream, and ingest into log aggregation systems.
Format
Each line is a valid JSON object:
{"event_id":"a1b2c3d4","timestamp":"2026-02-23T14:30:00Z","command":"git push --force origin main","matched_ids":["git:force_push"],"challenge_type":"Yes","outcome":"DENIED","context_labels":["branch=main"],"severity":"High","agent_name":"claude-code","agent_session_id":"sess-xyz-789","blast_radius_scope":"PROJECT","blast_radius_detail":"Overwrites remote branch history"}
Formatted for readability:
{
"event_id": "a1b2c3d4",
"timestamp": "2026-02-23T14:30:00Z",
"command": "git push --force origin main",
"matched_ids": ["git:force_push"],
"challenge_type": "Yes",
"outcome": "DENIED",
"context_labels": ["branch=main"],
"severity": "High",
"agent_name": "claude-code",
"agent_session_id": "sess-xyz-789",
"blast_radius_scope": "PROJECT",
"blast_radius_detail": "Overwrites remote branch history"
}
Field reference
Required fields (always present)
| Field | Type | Description |
|---|---|---|
event_id | string | Unique identifier for correlating pre/post challenge entries |
timestamp | string | ISO 8601 UTC timestamp (YYYY-MM-DDTHH:MM:SSZ) |
command | string | The full command string that was evaluated |
matched_ids | string[] | List of pattern IDs that matched (e.g., ["git:force_push"]) |
challenge_type | string | Challenge type applied: Math, Enter, Yes, or Deny |
outcome | string | Result of the evaluation (see Outcomes below) |
context_labels | string[] | Runtime context labels (e.g., ["branch=main", "ssh=true"]) |
severity | string | Highest severity among matched patterns: Info, Low, Medium, High, Critical |
Optional fields (present when applicable)
| Field | Type | Description |
|---|---|---|
agent_name | string | Name of the AI agent that originated the command |
agent_session_id | string | Session ID of the agent connection |
blast_radius_scope | string | Impact scope: RESOURCE, PROJECT, or MACHINE |
blast_radius_detail | string | Human-readable impact description |
Outcomes
| Outcome | Description |
|---|---|
ALLOWED | User passed the challenge or the command was below threshold |
DENIED | User failed the challenge, command was on deny list, or agent auto-deny blocked it |
SKIPPED | Pattern matched but severity was below min_severity threshold |
CANCELLED | Written before the challenge prompt; indicates the evaluation started but no decision was recorded yet |
Event correlation
Each command evaluation can produce two entries with the same event_id:
- A
CANCELLEDentry written before the challenge prompt begins - An
ALLOWEDorDENIEDentry written after the user responds
If only a CANCELLED entry exists (no matching ALLOWED/DENIED), the user hit Ctrl+C during the challenge.
For agent/MCP commands, only the final outcome entry is written (no CANCELLED pre-entry).
Context labels
Context labels are strings in key=value format:
| Label | Meaning |
|---|---|
ssh=true | Command was run in an SSH session |
root=true | Command was run as root |
branch=main | Current git branch is main |
k8s=prod-us-east-1 | Active Kubernetes context |
NODE_ENV=production | Production environment variable detected |
Example entries
User allows a risky command
{"event_id":"evt-001","timestamp":"2026-02-23T14:30:00Z","command":"rm -rf /tmp/old-data","matched_ids":["fs:recursively_delete"],"challenge_type":"Math","outcome":"ALLOWED","context_labels":[],"severity":"High"}
Command denied on production branch
{"event_id":"evt-002","timestamp":"2026-02-23T14:31:00Z","command":"git push --force origin main","matched_ids":["git:force_push"],"challenge_type":"Yes","outcome":"DENIED","context_labels":["branch=main"],"severity":"High"}
Agent command auto-denied
{"event_id":"evt-003","timestamp":"2026-02-23T14:32:00Z","command":"kubectl delete namespace production","matched_ids":["kubernetes:delete_namespace"],"challenge_type":"Deny","outcome":"DENIED","context_labels":["k8s=prod-us-east-1"],"severity":"Critical","agent_name":"claude-code","agent_session_id":"sess-abc-123","blast_radius_scope":"PROJECT","blast_radius_detail":"Deletes all resources in the namespace"}
Command skipped (below min_severity)
{"event_id":"evt-004","timestamp":"2026-02-23T14:33:00Z","command":"git stash drop","matched_ids":["git:stash_drop"],"challenge_type":"Math","outcome":"SKIPPED","context_labels":[],"severity":"Medium"}
Parsing examples
Python
import json
with open("~/.shellfirm/audit.log") as f:
for line in f:
event = json.loads(line.strip())
if event["outcome"] == "DENIED":
print(f"{event['timestamp']}: {event['command']}")
Shell (with jq)
# Parse each line
while IFS= read -r line; do
echo "$line" | jq -r '[.timestamp, .outcome, .command] | @tsv'
done < ~/.shellfirm/audit.log
Node.js
const fs = require('fs');
const lines = fs.readFileSync('~/.shellfirm/audit.log', 'utf8').trim().split('\n');
const events = lines.map(line => JSON.parse(line));
const denied = events.filter(e => e.outcome === 'DENIED');
console.log(`${denied.length} commands were denied`);