Supervisor-only assessment
grith enforces on one path. This is what that path carries, what has been closed, and what has not.
grith v0.3.1 has a single enforcement path. There is no second sandbox behind the supervisor: if ptrace and seccomp-BPF do not catch a call, nothing does. An internal assessment in early 2026 asked whether that one path could carry every control grith claims. This page reports where that stands on v0.3.1 - what the supervisor carries on its own, what has been closed since, and what is still open.
If you are auditing grith, read this alongside Limitations.
What the supervisor carries on its own
| Control | On v0.3.1 |
|---|---|
operation-risk, path-match, sensitive-path-heuristic | Full. The path is resolved from the syscall itself. |
argument, secret-scan, command | Full. They read the command line and argv the supervisor already reconstructs. |
destructive-action, dlp-gate, canary | Full over the mapped call types. |
egress-policy, reputation, egress-rate | Full for connects, HTTP requests, and datagram sends that name a destination. Bytes on an already-connected socket are not re-evaluated per send. |
behavioural, rate-limit, session-containment, taint | Full. All four key on the supervisor's own call stream, scoped per session. |
allowlist | Works, but starts empty. It carries only what your session grants and your saved allowlist put in it. |
capability | Mechanism works. Ships with no grants, and with no grants the filter is permissive. |
| Meta-rules and composite scoring | Full. They operate on filter output, so they are unaffected by how the call was captured. |
| Queue, human review, audit trail | Full. Every supervisor evaluation goes through the same decision model and the same hash-chained record. |
Closed since the assessment
Filter state is session-scoped. taint, rate-limit and behavioural key on a scope
attached to every call - the conversation id where there is one, otherwise the session's own
UUID. The taint registry was a global path map; it no longer is, and it expires at one hour.
Cross-session contamination is gone.
Datagram sends with an explicit destination are scored. A sendto naming an arbitrary
address used to be dropped as noise, which let a payload leave with no evaluation and no audit
record without even needing a connect. It now surfaces as a connect and runs the egress path.
Raw AF_PACKET sends do the same, and raw socket creation is denied at socket() for AF_PACKET
and for netlink families beyond routing.
Abstract-namespace unix sockets are matched. They render as unix:@<name> at classification,
so a control socket can no longer hide in the abstract namespace.
The delegating-binary classifier is no longer basename-only. A spawn is matched by argv
basename, by canonical path after symlink resolution, and by content SHA-256 against a set pinned
at session start - which defeats both ln -s systemd-run x and cp /usr/bin/systemd-run /tmp/x.
D-Bus is decided per method call. A connect to a bus endpoint arms message inspection instead of escalating the whole connection, and each method call is checked against an allowlist.
Escape enforcement is on by default. enforce_authority_delegating_spawn and
enforce_control_socket_connect both default to true, and a call that would be enforced
deliberately skips the session-allowlist short-circuit - otherwise a profile that lists docker or
tmux as routine would make the whole mechanism a silent no-op.
Interceptor failure terminates the tree. If the interceptor errors, the supervisor terminates
every tracee rather than exiting cleanly and leaving them running unsupervised. PTRACE_O_EXITKILL
covers the other direction: a supervisor crash SIGKILLs its tracees.
Still open
- No payload inspection.
read,writeandwritevare not trapped, so grith judges what is touched and not what is transferred. - The capability filter ships permissive. It enforces only once grants are declared.
- The mount/ownership and namespace coverage categories are off at defaults.
chown,mount,unshareandsetnsare not evaluated unless you turn them on. Cross-processptracecoverage, which shares the category-2 name, is on. - PTY ownership is audit-only unless
pty_ownership_enforce = true. - Five delegating-binary residuals remain, listed on Limitations.
The invariant it all rests on
Sticky session containment has no TTL - once a high-taint access arms it, it lasts until the session ends. That guarantee is only worth something if a supervised tool cannot start a fresh session for itself, so the invariant is: session lifecycle is user-initiated only.
grith execruns the supervisor loop exactly once and propagates the result to its exit code. There is no auto-retry path in the CLI or the supervisor.- The built-in agent loop retries on context overflow by shrinking the prompt inside the same session. It never allocates a new scope.
- The dashboard can start fresh sessions, but that endpoint is loopback-bound and token-gated, and it is an operator action.
SIGTERMandSIGINTarriving at the supervisor are logged at high severity asshutdown_signal_received, so the interesting case - a supervised tool signalling its own supervisor - leaves a trail.
The consequence: an attacker working through the model cannot wait out containment or reset it,
without your cooperation. The one thing that does reset it is a CI wrapper that retries
grith exec on failure. If your environment does that, know that each retry is a new session with
a clean scope.