grithdocs

Containment walkthrough

What changes in a session after it touches something sensitive - the 600-second window, and the sticky flag that never expires.

Two different mechanisms answer to the name containment, and they behave differently enough that it is worth walking through both. Neither is something you configure per session - both arm themselves when a session touches a sensitive path.

The windowed one: 600 seconds of stricter egress

Reading a file whose path matches a sensitive source arms a 600-second window on that session. Reading .env is the everyday case:

terminal
$ grith proxy test '{"type":"FileRead","path":"/home/u/project/.env"}'
Proxy Test Result
==================================================
Tool call:   FileRead(/home/u/project/.env)
Score:       6.0
Decision:    QUEUE (High)
Reason:      Score 6.0 in escalation zone
Eval time:   1.77ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

You approve it - it is your .env, and the agent needs the API base URL out of it. The read is allowed, and for the next ten minutes the session-containment filter adds a fixed amount to anything that could carry data off the machine. Every new trigger re-arms the window.

config/filters/containment.toml

toml
[containment]
containment_window_seconds = 600
network_score = 4.5
process_score = 4.0
shell_score   = 3.5

Those are the shipped values, read from the daemon's working directory if that file is present and from the copy compiled into the binary otherwise.

What that does to the calls that follow, using their real uncontained scores:

CallNormallyWhile contained
HttpRequest POST https://example.com/sink4.5, queue9.0, deny
HttpRequest GET https://registry.npmjs.org/react-1.5, allow3.0, allow
ProcessSpawn git push origin main1.0, allow5.0, queue
ProcessSpawn rustc src/main.rs1.0, allow1.0, allow

Two things worth reading off that table. A destination grith already trusts survives containment - the npm registry lands at exactly 3.0, which allows. And a build is not punished: spawn scrutiny applies only to binaries that can move data off the host, so a contained session running hundreds of compiler invocations produces no prompts at all.

Connects to a session D-Bus, X11 or tmux socket get 2.0 rather than 4.5. Those are local control channels, not egress, and the full amount used to push an ordinary keyring read past the deny line.

The sticky one: no timer, no way out

The second mechanism arms on a high taint - a path containing .ssh, id_rsa, id_ed25519, private_key or shadow - in any direction. Reading is the obvious case, but writing to ~/.ssh/authorized_keys arms it just as surely.

It does not need the access to be alarming. This read scores zero and is allowed:

terminal
$ grith proxy test '{"type":"FileRead","path":"/home/u/.ssh/config"}'
Proxy Test Result
==================================================
Tool call:   FileRead(/home/u/.ssh/config)
Score:       0.0
Decision:    ALLOW
Reason:      Score 0.0 below allow threshold
Eval time:   1.58ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

Allowed, and the session is now contained for the rest of its life. There is no TTL and no way to clear it short of ending the session. An attacker cannot wait it out.

What changes while it is set:

  • The session allowlist stops being consulted. Destinations the profile trusts, and targets you approved earlier in this session, re-run the whole pipeline instead of short-circuiting.
  • The read-only fast path is bypassed, so reads that would have been waved through as noise are scored.
  • Approve-replay is suspended. Answering yes to a call no longer covers an identical call 60 seconds later.
  • Exact-command approvals for delegating spawns are suspended.

Genuine noise paths - /proc, /sys, the tool's own PTY, CA certificate directories - are still short-circuited. Containment tightens judgement, it does not make grith re-examine the filesystem plumbing every tool touches.

Medium-sensitivity paths (.env, .aws, credentials, secrets, .gnupg, .kube/config) do not arm this. They arm the window above. That split is deliberate: a routine startup read of a credentials file should not put the session into its strictest mode for the rest of its life.

Seeing it

Contained sessions are marked in the session list, with the window's remaining seconds:

terminal
$ grith exec list

Each row carries the session id, tool, project, pid, uptime, intercepted-call counts, and CONTAINED (<n>s) when containment is active. The session_end audit record carries containment_triggered so you can tell afterwards whether a session ever went contained.

See also

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