Audit log
ShellWatch records every session that opens or closes and every signing-request outcome to an append-only audit log in the broker’s SQLite database. The Web UI exposes two views per account:
/audit/sessions— every session lifecycle event (open / close, source, who initiated, when)./audit/signings— everyPendingActionoutcome (approved,denied,expired,cancelled) with the full request context preserved at decision time.
This page explains what’s recorded, what isn’t, and what guarantees the audit log gives you.
What gets recorded
Section titled “What gets recorded”audit_session_lifecycle
Section titled “audit_session_lifecycle”Written by session-lifecycle-writer.ts, which subscribes to TerminalManager open/close events.
- Session ID, endpoint reference, source (
ui/mcp/ssh). - Timestamps for open and close.
- A
CloseReasonfor closes (explicit close, transport error, idle timeout, account deletion, …). TheTerminalManager.close()API requires aCloseReasonso the writer always gets a meaningful value — there are no “unknown” closes.
audit_signing_requests
Section titled “audit_signing_requests”Written by signing-requests-writer.ts, which subscribes to PendingActionStore resolutions.
Every PendingAction outcome is persisted with the full SignRequestContext (the same union the /sign/:id page renders from):
| Outcome | When |
|---|---|
approved | The user (or another approver on the account) tapped their authenticator and /api/actions/:id/resolve accepted the assertion. |
denied | The user pressed Deny on /sign/:id. |
expired | Nobody resolved within the 60-second TTL. |
cancelled | The requester (the SSH client / agent / MCP client) gave up before the user acted. |
Per-trigger metadata is snapshotted at decision time — endpoint label, agent client hostname/OS/version, MCP client name/version, source IP. A subsequent rename does not rewrite history.
What’s not recorded
Section titled “What’s not recorded”- Session content. Session input and output are never persisted by the broker — the in-memory buffer that feeds attached viewers is a runtime structure only, never written to disk. Tamper-evident recording of what was typed and what scrolled by is not a ShellWatch responsibility; if you need it, record on the target host (e.g.
script,tmux pipe-pane,auditd) where the bytes actually exist. - Failed authentications that never reached the
PendingActionlayer (e.g. a rejected bearer token on/mcp). Those live in the broker’s structured logs. - WebAuthn registrations / login ceremonies. They show up in
/admin/generaland the structured logs, but not in the audit tables.
Account scoping
Section titled “Account scoping”Reads are account-scoped. There’s no global “see every account’s audit” view — admin is a role on the account, not a cross-tenant lens. A self-hosted single-account install is effectively a single audit stream; a multi-account install gives each account its own.
A determined operator with shell access to the broker host can of course read the SQLite file directly. The audit log is defense-in-depth, not a primary control against a compromised broker — see Security model.
Read-time guarantees
Section titled “Read-time guarantees”The audit module never joins to live tables at read time. If you rename a passkey or relabel an endpoint, prior audit rows still display the names that were in effect when the decision happened.
In practice this means the endpoint “foo” you approved a session against last Tuesday is still labelled “foo” in the log even after it gets relabelled to “bar”.
Pagination
Section titled “Pagination”/audit/sessions and /audit/signings use keyset pagination (cursor-based, not offset), so the read API is O(page size) regardless of how deep into history you scroll. Useful for self-hosted instances that accumulate a long tail.
Default page size is 50 rows; clients can override with ?limit= up to a server-side cap of 500. There’s no operator-tunable knob for the cap — if you need a different ceiling you’d recompile.
Backup and retention
Section titled “Backup and retention”The audit tables live in the same SQLite database as the rest of the broker state. Back up data/shellwatch.db (and the WAL/SHM sidecars) like any other table. There is no built-in retention policy yet — append-only with no automatic pruning. If you need formal retention, snapshot and offload to your own pipeline.
Where to next
Section titled “Where to next”- Concepts → Security model — what the audit log does and doesn’t protect against.
- Concepts → Sign requests — the upstream of every
audit_signing_requestsrow. - HTTP endpoints —
/api/audit/*REST API.