Audit System
Overview
Section titled “Overview”lash includes a tamper-evident audit system for logging hook events. The audit log is a single append-only JSONL file at ~/.lash/audit.log, protected by a secret-ratcheted hash chain. Implemented as a built-in plugin.
Integrity Model
Section titled “Integrity Model”salt = random 16-byte hexsecret_0 = KDF(password, salt) -- 600,000 iterations HMAC-SHA256hash_1 = HMAC-SHA256(secret_0, content_1)secret_1 = HMAC-SHA256(secret_0, hash_1) -- ratchet forwardhash_2 = HMAC-SHA256(secret_1, content_2)secret_2 = HMAC-SHA256(secret_1, hash_2)...Each secret ratchets forward — only the current value exists. Previous secrets are destroyed.
| Scenario | Outcome |
|---|---|
| Attacker has log only | Cannot forge any hash |
| Attacker has key file only | Can forge future, not past |
| Attacker has both | Can forge future, past is locked |
| User verifies with password | Full chain validated |
The password is never stored. A verification hash (derived from password + ":verify") is stored for password-gated role escalation.
Key File
Section titled “Key File”~/.lash/audit.key (permissions 0600):
<salt>:<64-char hex secret>:<entry count>:<password verify hash>Updated atomically (write .tmp, then rename()). On startup, entry count is compared against actual log lines; a mismatch indicates tampering.
Log Entry Format
Section titled “Log Entry Format”{ "action": "pipeline.pre_execute", "ts": "2026-03-06T14:30:00.123", "seq": "42", "sid": "s_123", "command": "ls -la", "cwd": "/home/user", "hash": "a3f2...c9d8"}| Field | Description |
|---|---|
action | Hook event name |
ts | Timestamp (ISO 8601 extended) |
seq | Sequence number (monotonically increasing) |
sid | Session ID |
hash | HMAC-SHA256 hash from the ratcheted secret chain |
Subscribed Hooks
Section titled “Subscribed Hooks”The audit plugin observes: session.connect, session.disconnect, pipeline.pre_execute, pipeline.post_execute, variable.set, variable.drop, job.background, job.foreground, error.dispatch.
Commands
Section titled “Commands”All audit commands require the auditAccess capability. The password is collected interactively (echo disabled) — never via CLI args or env vars.
| Command | Description |
|---|---|
audit init | Initialize audit system. Generates salt, derives secret, writes key file. |
audit verify | Recomputes full ratchet chain, verifies every entry. Exit 0 if intact, 1 if tampered. |
audit rotate | Verifies log, deletes if valid, resets chain with new salt. |
Verify Output
Section titled “Verify Output”Audit Report===================================Entries: 1247Period: 2026-01-15T08:00:00 -> 2026-03-06T14:30:00Status: INTACT
Events by type: pipeline.post_execute: 489 pipeline.pre_execute: 489 session.connect: 120 session.disconnect: 118
Violations: 0Automatic Rotation Checks
Section titled “Automatic Rotation Checks”On startup, the audit plugin warns via stderr when thresholds are exceeded:
| Condition | Default Threshold |
|---|---|
| File size | 50 MB |
| File age | 7 days |
| Entry count mismatch | (always) |
Configure thresholds in ~/.lash/config:
[settings]audit.max_size_mb = 50audit.max_age_days = 7Audit vs Session Logs
Section titled “Audit vs Session Logs”| Audit Log | Session Logs | |
|---|---|---|
| Purpose | Tamper-evident security trail | Command history/replay |
| Scope | All sessions, all hook events | One session’s commands |
| Integrity | Secret-ratcheted hash chain | Plain JSONL |
| Audience | Sysadmins, compliance | The user |
| Location | ~/.lash/audit.log | ~/.lash/sessions/*.jsonl |