Architecture overview
One daemon owns the audit log, the digest and the filter pipeline; every supervised session is a thin client that fails closed without it.
grith is one binary in three roles. A long-lived daemon owns every piece of shared
state. A supervisor wraps an agent in ptrace and seccomp and asks the daemon for
each decision. The CLI is the third: exec, supervisor and reputation go through
the daemon over loopback HTTP; every other command builds its own subsystems and reads the
same files from disk.
Dual-Path Architecture
Hover over any node to see details. Both paths converge on the same security proxy.
< 3.0
3.0 - 8.0
> 8.0
The daemon owns the shared state
One process per user, started by grith daemon start or auto-started by the first
supervised session. It holds:
- the audit database, as the single exclusive writer
- the digest queue of calls waiting for a review
- the security proxy and its 18 filters
- the session registry, which enforces the concurrency cap
- the canary registry and the reputation table
- the HTTP and WebSocket server on
127.0.0.1:3141, which also serves the dashboard
grith dashboard is a retained alias for grith daemon, so both spellings work.
Sessions are thin clients
grith exec does the ptrace and seccomp work locally and owns nothing else. Its digest
store, audit sink and session record are all remote shims over the daemon.
Transport is loopback HTTP with a bearer token - there is no unix socket. The token
lives in ~/.config/grith/daemon.token and is rotated on every daemon start.
grith exec fails closed. Without a running daemon of the exact same version it never
spawns the target at all: there is no in-process fallback and no unsupervised mode. That
includes server.enabled = false, which makes grith exec refuse outright.
How one call is decided
- The supervised process traps. The child installs a seccomp-BPF filter that returns
TRACE for 84 curated syscall identities and ALLOW for everything else;
execve,cloneandforkarrive as ptrace events instead. - The supervisor reads the registers and builds a typed tool call -
FileRead,ShellExec,NetConnectand so on. - Cheap local answers come first: noise paths, the session allowlist the profile declares, and the session's own reviewer memory. A session-allowlist match means the proxy is never invoked - no filters, no score.
- Anything left goes to the daemon over loopback HTTP.
- Filters run in three phases. A phase that pushes the score past the deny line ends the evaluation there - later phases and the meta-rules never run.
- The five meta-rules run once, after phase 3.
- The composite is compared to the thresholds: allow at or below 3.0, queue above 3.0 up to and including 8.0, deny above 8.0.
- The decision returns to the supervisor. ALLOW resumes the syscall. DENY rewrites the
syscall number so the kernel skips it and sets the return register to
EPERM- one failed call, not a kill, except an authority-delegating spawn caught at exec, which is SIGKILLed. QUEUE holds the calling thread at its kernel stop until a reviewer answers; its sibling threads keep running. - Every proxy-evaluated decision is appended to the hash-chained audit database, and a materialiser folds the audit tail into the local analytics projection.
Early termination is visible in the output. This read never reaches phase 2, so six
filters print under an 18 active header:
$ grith proxy test '{"type":"FileRead","path":"/home/u/.ssh/id_rsa"}'
Proxy Test Result
==================================================
Tool call: FileRead(/home/u/.ssh/id_rsa)
Score: 9.0
Decision: DENY
Reason: Access to SSH private key; read access to key/certificate file
Eval time: 0.03ms
Thresholds: allow < 3, deny > 8
Filters: 18 active
Filter Breakdown:
. operation-risk 0.0
+ path-match 5.0 [critical] Access to SSH private key
+ sensitive-path-heuristic 4.0 [error] read access to key/certificate file
. allowlist 0.0
. argument 0.0
. capability 0.0
Exit code: 2 (deny)Where state lives
| Path | Holds |
|---|---|
~/.config/grith/config.toml | Your configuration overlay |
~/.config/grith/daemon.token | IPC bearer token, rotated each daemon start |
~/.config/grith/dashboard.token | Browser token, stable across restarts |
~/.config/grith/daemon.json | Instance id, pid, port, version, owned audit DB |
~/.config/grith/reputation.toml | The learned reputation table |
~/.local/share/grith/audit/audit.db | Hash-chained audit log and the analytics projection |
~/.local/share/grith/audit/digest.db | The quarantine queue |
~/.local/share/grith/audit/cold/ | Archived records older than the full-retention window |