Audit Log
Rafter writes a security audit log to~/.rafter/audit.jsonl in JSONL format (newline-delimited JSON). Every security-relevant action — policy enforcement decisions, secret detections, 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/ is created automatically on first use.
Schema
Every audit log entry contains these fields:Base Fields
| Field | Type | Required | Description |
|---|---|---|---|
timestamp | string | yes | ISO 8601 UTC timestamp (e.g., 2026-02-20T10:30:45.123Z) |
sessionId | string | yes | Unique per CLI invocation. Format: {epoch_ms}-{random} |
eventType | string | yes | Event type identifier (see Event Types) |
agentType | string | no | AI agent platform: "openclaw" or "claude-code" |
action Object
Present on most events. Contains context about what triggered the event.
| Field | Type | Required | Description |
|---|---|---|---|
command | string | no | Shell command string |
tool | string | no | Tool name (e.g., Write, Bash) |
riskLevel | string | no | "low", "medium", "high", or "critical" |
securityCheck Object
Always present. Records the outcome of the security evaluation.
| Field | Type | Required | Description |
|---|---|---|---|
passed | boolean | yes | Whether the security check passed |
reason | string | no | Human-readable explanation |
details | object | no | Structured metadata (event-specific) |
resolution Object
Always present. Records what action was taken.
| Field | Type | Required | Description |
|---|---|---|---|
actionTaken | string | yes | "blocked", "allowed", "overridden", or "redacted" |
overrideReason | string | no | User-provided justification (only on policy_override) |
Event Types
command_intercepted
Emitted when a shell command is evaluated against the security policy.
| Field | Value |
|---|---|
action.command | The shell command string |
action.riskLevel | Dynamically assessed: low, medium, high, or critical |
securityCheck.passed | true if allowed, false if blocked |
securityCheck.reason | Why the command was blocked/flagged |
resolution.actionTaken | "allowed", "blocked", or "overridden" |
secret_detected
Emitted when a secret is found in files, staged content, or tool output.
| Field | Value |
|---|---|
action.riskLevel | Always "critical" |
securityCheck.passed | Always false |
securityCheck.reason | "{secret_type} detected in {location}" |
resolution.actionTaken | "blocked" or "allowed" |
content_sanitized
Emitted when sensitive patterns are redacted from output.
| Field | Value |
|---|---|
securityCheck.passed | Always false |
securityCheck.reason | "{n} sensitive patterns detected" |
securityCheck.details | { "contentType": "...", "patternsMatched": n } |
resolution.actionTaken | Always "redacted" |
policy_override
Emitted when a user explicitly overrides a security policy (e.g., --force flag).
| Field | Value |
|---|---|
action.command | The overridden command (optional) |
action.riskLevel | Always "high" |
securityCheck.passed | Always false |
securityCheck.reason | "Security policy overridden by user" |
resolution.actionTaken | Always "overridden" |
resolution.overrideReason | User-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_detectedevents record the secret type and file location, not the secret itself. - Content is not stored.
content_sanitizedevents record pattern counts and content types, not the raw content. - Commands are logged verbatim.
command_intercepted(policy enforcement) 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
retentionDaysare purged whencleanup()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:
| Key | Type | Default | Description |
|---|---|---|---|
logAllActions | boolean | true | Master switch. If false, no events are written. |
retentionDays | number | 30 | Days to retain entries before cleanup purges them. |
logLevel | string | "info" | Stored in config but not currently used for filtering. |
.rafter.yml):
Webhook Notifications
When configured, the audit logger sends a POST request to a webhook URL for events at or above a minimum risk level. Works with Slack incoming webhooks, Discord webhooks, and generic HTTP endpoints.Configuration
| Key | Type | Default | Description |
|---|---|---|---|
agent.notifications.webhook | string | — | Webhook URL to POST notifications to |
agent.notifications.minRiskLevel | string | "high" | Minimum risk level to trigger notification ("high" or "critical") |
Webhook Payload
text field provides Slack compatibility. The content field provides Discord compatibility. Both contain a human-readable summary.
Webhook delivery is fire-and-forget with a 5-second timeout. Failures are silently ignored to avoid disrupting audit logging.
Setup
Querying the Audit Log
Userafter agent audit to view and filter entries:
MCP Access
Theread_audit_log MCP tool exposes audit log entries to MCP clients:

