Session containment
Two different mechanisms share the name: a sticky session-lifetime lock armed by high taint, and a 600-second scoring window.
Two separate things are called containment, and they behave differently. One is a session-lifetime state that suspends grith's shortcuts; the other is a filter that adds score for ten minutes. They can be active at the same time and neither implies the other.
Sticky containment
Armed by registering a high-level taint - a read, write, chmod, rename or delete
touching .ssh, id_rsa, id_ed25519, private_key or shadow. Direction does not
matter: a write to ~/.ssh/authorized_keys arms it as surely as a read of ~/.ssh/id_rsa.
Medium-level taint - .env, .aws, credentials, secrets, .gnupg, .kube/config -
does not arm it. That is deliberate, to keep ordinary startup reads of agent
credential files from locking a session down.
It has no TTL. Once set it stays set. An attacker cannot wait it out. It clears only
when the session ends, and the first reason is kept for forensics; the session_end audit
record carries a containment_triggered flag.
What it does is suspend the shortcuts, so the pipeline gets to see everything:
- The
ignore_read_onlyfast path is bypassed. - The session allowlist is not consulted at all. Even a profile-trusted destination
like
api.openai.comre-runs the full pipeline. - Exact-command delegating approvals are suspended.
- Approve replay and IPC grants are suspended.
One thing is not gated: the noise-path short-circuit. /proc, /sys, /dev/pts,
/dev/tty and CA certificate directories stay noise regardless, because they were never
carrying anything.
⚠️A retry wrapper clears it
Session lifecycle is user-initiated. A CI wrapper that automatically re-runs
grith exec on failure starts a new session, which resets the scope and therefore
clears containment. That is an operator choice, not a bug, but it is worth knowing you
have made it.
Windowed containment
The session-containment filter is the second mechanism. It is one of the 18 and it
contributes score like any other.
Armed by a read of a sensitive source, matched on path segment or basename, or by any call carrying taint. The window is 600 seconds and re-arms on each new trigger.
While the window is open it adds:
| Operation | Added |
|---|---|
| Network or HTTP egress | +4.5 |
| Outbound-capable process spawn | +4.0 |
| Shell execution | +3.5 |
| Control-class unix socket - session D-Bus, X11, tmux, screen | +2.0 |
Control sockets get the reduced figure on purpose: a keyring read under containment should not be pushed past the auto-deny line by the window alone.
Spawn scrutiny applies only to outbound-capable binaries. A contained session running
a build - hundreds of rustc and linker spawns - is not penalised for any of them.
Which one you are looking at
| Sticky | Windowed | |
|---|---|---|
| Where it lives | Session state | Filter 16 in the pipeline |
| Trigger | High taint only | Sensitive read or any taint |
| Duration | The whole session | 600 seconds, re-armable |
| Effect | Suspends allowlists and replay | Adds 2.0 to 4.5 to the composite |
Visible in proxy test | No | Yes, as session-containment |
If prompts are appearing for destinations you allowlisted, that is sticky containment. If scores are 4-ish higher than you expect but the allowlist still works, that is the window.
Releasing the window
The unlock-egress action on a digest item approves the call and unregisters the
containment tracker for that session, which closes the window. It does not clear sticky
containment - nothing does, short of ending the session.