grithdocs

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.

Path 1 - Built-in Agent
User Prompt
LLM Provider
Tool Call
Path 2 - CLI Supervisor
grith exec
CLI Tool
Syscall Intercept
Security Proxy
Decision Engine
Audit Log
Digest Queue
✓ ALLOW

< 3.0

⏸ QUEUE

3.0 - 8.0

⛔ DENY

> 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

  1. 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, clone and fork arrive as ptrace events instead.
  2. The supervisor reads the registers and builds a typed tool call - FileRead, ShellExec, NetConnect and so on.
  3. 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.
  4. Anything left goes to the daemon over loopback HTTP.
  5. 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.
  6. The five meta-rules run once, after phase 3.
  7. 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.
  8. 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.
  9. 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:

terminal
$ 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

PathHolds
~/.config/grith/config.tomlYour configuration overlay
~/.config/grith/daemon.tokenIPC bearer token, rotated each daemon start
~/.config/grith/dashboard.tokenBrowser token, stable across restarts
~/.config/grith/daemon.jsonInstance id, pid, port, version, owned audit DB
~/.config/grith/reputation.tomlThe learned reputation table
~/.local/share/grith/audit/audit.dbHash-chained audit log and the analytics projection
~/.local/share/grith/audit/digest.dbThe quarantine queue
~/.local/share/grith/audit/cold/Archived records older than the full-retention window

See also

Last updated: 2026-08-24Edit this page on GitHub →