grithdocs

Filter overview

The 18 filters grith runs on every supervised call, in the order they run, and what each one can add to the score.

Every supervised call is scored by 18 filters. All 18 are on by default - every config toggle in this section is an off switch, not an on switch. A filter's only job is to return a number. The sum of those numbers is the composite score, and the composite alone decides the outcome:

  • allow - composite 3.0 or below
  • queue - composite above 3.0, up to and including 8.0
  • deny - composite above 8.0

Both boundaries are strict >, so exactly 3.0 allows and exactly 8.0 queues.

The 18 filters

They run in three phases. Filters inside a phase run concurrently; the phases run in order. This is also the order grith proxy test prints them.

#FilterPhaseScoreWhat it looks at
1operation-riskstatic0.0 - 5.0The kind of call - read, write, spawn, chown, mount
2path-matchstatic3.0 - 5.015 anchored path rules in paths.toml
3sensitive-path-heuristicstatic0.5 - 5.0Built-in heuristics for credential-shaped paths
4allowliststatic-1.0 / +3.0Your own allow and deny patterns
5argumentstatic1.0 - 2.0Oversized arguments, metacharacters, traversal
6capabilitystatic10.0Whether the caller was granted this class of operation
7secret-scanpattern1.0 - 5.01618 credential patterns over the call's arguments
8commandpattern2.0 - 4.012 shell-shape rules in commands.toml
9destructive-actionpattern3.5 - 9.0Format, wipe, drop, teardown - and whether it targets production
10egress-policypattern-1.0 - 7.0Where the call is going, and what shape the payload is
11dlp-gatepattern1.0 / 5.0 / 9.0Secrets in outbound arguments
12canarypattern9.5Your canary tokens leaving the machine
13reputationcontext-1.0 - 4.0Destination against the shipped domain lists
14behaviouralcontext1.0 - 3.0Authority-changing calls against the session baseline
15taintcontext1.5 - 5.0Sensitive data read earlier in the session, leaving now
16session-containmentcontext2.0 - 4.5Egress in the 600s after a sensitive read
17rate-limitcontext1.0 - 3.0Per-category call volume
18egress-ratecontext1.5 - 5.0Outbound volume, destination spread, read-then-send

After phase 3, the meta-rules adjust the composite once.

What the phases are for

Static filters judge the shape of the call - operation type, path string, argument length. No content is read and no session history is consulted, so the same call always scores the same.

Pattern filters read the call's arguments: regex sets, a shell tokeniser, destination parsing. This is where a payload gives itself away.

Context filters need accumulated state - what this session has already read, how many calls it has made, where it has already connected.

Early termination

At the end of each phase grith checks the running total. If it is already above 8.0, the call is denied there and then - later phases never run and the meta-rules never run. That is why a real deny prints fewer than 18 lines 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.02ms
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)

Six lines, not eighteen. Phase 1 reached 9.0 and stopped.

Two things filters cannot do

A filter cannot return a verdict. There is no allow/deny/queue field on a filter result - only a score. What reads as a hard deny is always a score deliberately set above the deny line: capability at 10.0, canary at 9.5, destructive-action at 9.0.

A filter's score is not capped, and the sum is not normalised. The composite is a plain sum of every matched filter's score. It can also go down: allowlist, egress-policy and reputation each emit -1.0 when they recognise something as routine.

See also

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