Skip to main content

Audit Log

Rafter writes a security audit log to ~/.rafter/audit.jsonl in JSONL format (newline-delimited JSON). Every security-relevant action—command interceptions, secret detections, policy overrides—is recorded as a single JSON object per line. Both the Node and Python CLIs write to the same file using the same schema. The Node CLI is the reference implementation; the Python CLI currently emits command_intercepted and secret_detected events only.

File Location

~/.rafter/audit.jsonl
The directory ~/.rafter/ is created automatically on first use.

Schema

Every audit log entry contains these fields:

Base Fields

FieldTypeRequiredDescription
timestampstringyesISO 8601 UTC timestamp (e.g., 2026-02-20T10:30:45.123Z)
sessionIdstringyesUnique per CLI invocation. Format: {epoch_ms}-{random}
eventTypestringyesEvent type identifier (see Event Types)
agentTypestringnoAI agent platform: "openclaw" or "claude-code"

action Object

Present on most events. Contains context about what triggered the event.
FieldTypeRequiredDescription
commandstringnoShell command string
toolstringnoTool name (e.g., Write, Bash)
riskLevelstringno"low", "medium", "high", or "critical"

securityCheck Object

Always present. Records the outcome of the security evaluation.
FieldTypeRequiredDescription
passedbooleanyesWhether the security check passed
reasonstringnoHuman-readable explanation
detailsobjectnoStructured metadata (event-specific)

resolution Object

Always present. Records what action was taken.
FieldTypeRequiredDescription
actionTakenstringyes"blocked", "allowed", "overridden", or "redacted"
overrideReasonstringnoUser-provided justification (only on policy_override)

Event Types

command_intercepted

Emitted when a shell command is evaluated against the security policy.
FieldValue
action.commandThe shell command string
action.riskLevelDynamically assessed: low, medium, high, or critical
securityCheck.passedtrue if allowed, false if blocked
securityCheck.reasonWhy the command was blocked/flagged
resolution.actionTaken"allowed", "blocked", or "overridden"
Example:
{
  "timestamp": "2026-02-20T10:30:45.123Z",
  "sessionId": "1740047445123-k8f2m",
  "eventType": "command_intercepted",
  "agentType": "claude-code",
  "action": {
    "command": "git push --force",
    "riskLevel": "high"
  },
  "securityCheck": {
    "passed": false,
    "reason": "High-risk command requires approval"
  },
  "resolution": {
    "actionTaken": "blocked"
  }
}

secret_detected

Emitted when a secret is found in files, staged content, or tool output.
FieldValue
action.riskLevelAlways "critical"
securityCheck.passedAlways false
securityCheck.reason"{secret_type} detected in {location}"
resolution.actionTaken"blocked" or "allowed"
The audit log never contains the raw secret value—only the type (e.g., “AWS Access Key”) and location (e.g., “staged files”, “config.js”). Example:
{
  "timestamp": "2026-02-20T10:25:12.456Z",
  "sessionId": "1740047445123-k8f2m",
  "eventType": "secret_detected",
  "agentType": "openclaw",
  "action": {
    "riskLevel": "critical"
  },
  "securityCheck": {
    "passed": false,
    "reason": "AWS Access Key detected in config.js"
  },
  "resolution": {
    "actionTaken": "blocked"
  }
}

content_sanitized

Emitted when sensitive patterns are redacted from output.
FieldValue
securityCheck.passedAlways false
securityCheck.reason"{n} sensitive patterns detected"
securityCheck.details{ "contentType": "...", "patternsMatched": n }
resolution.actionTakenAlways "redacted"
Example:
{
  "timestamp": "2026-02-20T11:00:00.000Z",
  "sessionId": "1740047445123-k8f2m",
  "eventType": "content_sanitized",
  "securityCheck": {
    "passed": false,
    "reason": "3 sensitive patterns detected",
    "details": {
      "contentType": "shell_output",
      "patternsMatched": 3
    }
  },
  "resolution": {
    "actionTaken": "redacted"
  }
}

policy_override

Emitted when a user explicitly overrides a security policy (e.g., --force flag).
FieldValue
action.commandThe overridden command (optional)
action.riskLevelAlways "high"
securityCheck.passedAlways false
securityCheck.reason"Security policy overridden by user"
resolution.actionTakenAlways "overridden"
resolution.overrideReasonUser-provided reason string

scan_executed

Reserved for future use. Will be emitted when file scans are performed.

config_changed

Reserved for future use. Will be emitted when security configuration is modified.

Redaction Behavior

The audit log is designed to be safe to retain and share:
  • Secret values are never logged. secret_detected events record the secret type and file location, not the secret itself.
  • Content is not stored. content_sanitized events record pattern counts and content types, not the raw content.
  • Commands are logged verbatim. command_intercepted events include the full command string. If commands contain sensitive arguments, they appear in the log.

Size and Rotation

  • No automatic rotation. The log file grows unbounded until cleanup runs.
  • Time-based retention: Entries older than retentionDays are purged when cleanup() is called.
  • No automatic scheduling. Cleanup must be triggered manually or via the API.
  • Default retention: 30 days.

Configuration

Configure audit logging in ~/.rafter/config.json under agent.audit, or in .rafter.yml under audit:
KeyTypeDefaultDescription
logAllActionsbooleantrueMaster switch. If false, no events are written.
retentionDaysnumber30Days to retain entries before cleanup purges them.
logLevelstring"info"Stored in config but not currently used for filtering.
Config file example:
{
  "agent": {
    "audit": {
      "logAllActions": true,
      "retentionDays": 90,
      "logLevel": "info"
    }
  }
}
Policy file example (.rafter.yml):
audit:
  retention_days: 90
  log_level: info

Querying the Audit Log

Use rafter agent audit to view and filter entries:
# Show last 10 entries (default)
rafter agent audit

# Show last 50 entries
rafter agent audit --last 50

# Filter by event type
rafter agent audit --event secret_detected

# Filter by agent
rafter agent audit --agent claude-code

# Entries since a specific date
rafter agent audit --since 2026-02-01

# Combine filters
rafter agent audit --event command_intercepted --agent openclaw --last 100
Or query the JSONL file directly with standard tools:
# Count blocked commands
grep '"actionTaken":"blocked"' ~/.rafter/audit.jsonl | wc -l

# Find all secret detections with jq
jq 'select(.eventType == "secret_detected")' ~/.rafter/audit.jsonl

# Events from the last 24 hours
jq --arg cutoff "$(date -u -v-1d +%Y-%m-%dT%H:%M:%S)" \
  'select(.timestamp > $cutoff)' ~/.rafter/audit.jsonl

MCP Access

The read_audit_log MCP tool exposes audit log entries to MCP clients:
{
  "tool": "read_audit_log",
  "arguments": {
    "event_type": "secret_detected",
    "limit": 20
  }
}
See MCP Integration for setup.