Security and Audit
Session Roles
Section titled “Session Roles”Each frontend connection is assigned a role. Five built-in roles control what actions a session can perform:
| Role | Description |
|---|---|
admin | Full access. Config, other sessions, audit log. |
user | Standard interactive use. Own processes, plugins, jobs. |
script | (default) Like user without job control, plugins, or admin. |
ci | Restricted spawn via allowlist. Full redirect and env. |
restricted | Minimal. No arbitrary processes, no redirects, no env mutation. |
Capabilities
Section titled “Capabilities”| Capability | Description |
|---|---|
spawnAny | Allow spawning any process from PATH. |
spawnAllowlist | Comma-separated permitted commands (when spawnAny is false). |
fileRedirect | Allow >, >> file writes. |
fileSandbox | Restrict writes to this directory. Textual path normalization only — symlink traversal is not resolved and may bypass the sandbox. |
envMutation | Allow modifying env vars. |
envSafelist | Comma-separated env vars that can be modified. |
pluginAccess | Allow calling plugin methods. |
pluginAllowlist | Comma-separated permitted plugins. |
configModify | Allow modifying config at runtime. |
sessionManage | Allow managing other sessions (list, kill). |
jobControlOwn | Allow fg/bg/jobs for own jobs. |
jobControlOthers | Allow managing other sessions’ jobs. |
auditAccess | Allow audit commands and log access. |
Custom Roles
Section titled “Custom Roles”Defined in ~/.lash/roles.conf:
[role:ci]spawnAny = falsespawnAllowlist = make, cmake, ninja, gcc, g++, clang, git, cargo, dubfileRedirect = truefileSandbox = /home/ci/buildsenvMutation = trueenvSafelist = PATH, HOME, CC, CXXpluginAccess = false
[role:monitoring]spawnAny = falsespawnAllowlist = ps, top, df, free, uptimefileRedirect = falseauditAccess = trueCustom roles override built-in defaults of the same name.
Role Switching
Section titled “Role Switching”role # prints current role (no password needed)role user # switch (no password needed)role admin # REQUIRES audit passwordSwitching to a role with auditAccess requires entering the audit password, verified against the stored hash in ~/.lash/audit.key.
Denied Actions
Section titled “Denied Actions”When a role denies an action, the backend returns a Problem/Suggestion error identifying the role and the denied capability:
Problem: This session (role: restricted) cannot run 'curl'.Suggestion: 'curl' is not in the allowlist. Switch to a 'user' session.The default role is configured in ~/.lash/config:
[settings]role.default = scriptAudit System
Section titled “Audit System”A tamper-evident audit system for logging all 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 (audit).
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)...The KDF uses 600,000 iterations (OWASP 2023 recommendation for SHA-256). 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; 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 subscribes as an observer to: session.connect, session.disconnect, pipeline.pre_execute, pipeline.post_execute, variable.set, variable.drop, job.background, job.foreground, error.dispatch.
Audit Commands
Section titled “Audit 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. Fails if already initialized. |
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. |
Automatic Rotation Checks
Section titled “Automatic Rotation Checks”On startup, the audit plugin warns via stderr:
| Condition | Default Threshold |
|---|---|
| File size | 50 MB |
| File age | 7 days |
| Entry count mismatch | (always) |
Thresholds configurable in ~/.lash/config:
[settings]audit.max_size_mb = 50audit.max_age_days = 7Architecture
Section titled “Architecture”runtime/integrity.d -- HMAC-SHA256, KDF, ratchet, key file I/O ^protocol/paths.d -- auditLogPath(), auditKeyPath() ^backend/audit.d -- auditInit, auditRotate, report buildingplugins/audit/main.d -- AuditPlugin (HookDispatch), event logging ^backend/daemon/builtins.d -- 'audit' and 'role' command routingBoundary with Session Logs
Section titled “Boundary with 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 |