grithdocs

Filter config files

The ten TOML files filters load, where an override has to sit, and why an override replaces rather than extends.

Filters that carry too much data for config.toml load it from separate files. The shipped copies are compiled into the binary - the installer writes nothing to disk - and the only override that a plain install honours is a ./config/filters/ directory relative to the working directory grith runs in.

The files

FileRoot shapeHolds
paths.toml[[rules]]15 path rules, scored 3.0-5.0
secrets.toml[[patterns]]1618 credential regexes
commands.toml[[rules]]12 dangerous-command patterns, 2.0-4.0
meta_rules.toml[[meta_rules]]5 composite rules, each with its own [[meta_rules.conditions]]
domains.toml[known_safe], [known_malicious]27 safe domains, 13 malicious
egress.toml[egress]Mode, 51 trusted domains, scheme and port lists, entropy bounds
containment.toml[containment]The 600s window, 13 sensitive sources, three scores
dlp.toml[dlp]enabled and policy
canary.toml[canary]enabled, plus optional [[canary.tokens]]
capabilities.toml[[grants]]Empty as shipped, which is why the capability filter runs permissive

There is no single common shape - the root key differs per file, and getting it wrong fails quietly either way: a file missing a required root key (rules, patterns, meta_rules) does not parse and grith falls back to the built-ins, while domains.toml and capabilities.toml default every field and load as an empty ruleset.

An eleventh file, allowlist.toml, is not shipped. It is optional at ./config/filters/allowlist.toml and at ~/.config/grith/filters/allowlist.toml, which is where an always-allow answer at a prompt is persisted. It is the only file with a home-directory layer, and the only one that merges rather than replaces.

Where grith looks

For every file except allowlist.toml, three candidates in order, first one that parses wins:

  1. ./config/filters/<file>.toml - relative to the current working directory
  2. the same path relative to the source checkout, if you are running from one
  3. the copy embedded in the binary

Paths are hard-coded. config.toml has no key that points at any of them - the config_file and domains_file keys you may see in old configs are dead.

⚠️An override replaces the whole file

grith does not merge your file with the built-in one. The first candidate that parses is the entire ruleset. Write a two-rule paths.toml and you have two path rules, not seventeen.

Prove it to yourself. Baseline, with no override anywhere:

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

Now with a ./config/filters/paths.toml holding one unrelated rule:

terminal
$ grith proxy test '{"type":"FileRead","path":"/home/u/.ssh/id_rsa"}'
Proxy Test Result
==================================================
Tool call:   FileRead(/home/u/.ssh/id_rsa)
Score:       4.0
Decision:    QUEUE (Medium)
Reason:      Score 4.0 in escalation zone
Filters:     18 active

Filter Breakdown:
. operation-risk 0.0
. path-match 0.0 + sensitive-path-heuristic 4.0 [error] read access to key/certificate file

The SSH rule is gone, the score drops below the deny line, and nothing warned you. A file that fails to parse is worse: grith falls through to the next candidate and uses the built-ins, silently. Test every edit with grith proxy test.

Adding a rule

Copy the shipped file first, then add yours - and check with grith proxy test that the built-in rules survived.

config/filters/paths.toml

toml
[[rules]]
id = "customer-data"
pattern = "~/work/customer-data/*"
operations = ["read", "write", "delete"]
score = 5.0
severity = "critical"
message = "Customer data must not leave the machine"
terminal
$ grith proxy test '{"type":"FileRead","path":"/home/u/work/customer-data/accounts.csv"}'
Proxy Test Result
==================================================
Tool call:   FileRead(/home/u/work/customer-data/accounts.csv)
Score:       5.0
Decision:    QUEUE (Medium)
Reason:      Score 5.0 in escalation zone
Filters:     18 active

Filter Breakdown:
. operation-risk 0.0 + path-match 5.0 [critical] Customer data must not leave the machine
. sensitive-path-heuristic 0.0

An internal secret shape goes in secrets.toml:

config/filters/secrets.toml

toml
[[patterns]]
id = "acme-internal-id"
regex = "ACME-CID-[0-9]{8}"
score = 4.0
severity = "critical"
message = "Internal customer ID format detected"

An extra trusted destination goes in egress.toml's trusted_domains array, a never-trusted one in blocked_domains. Both are flat arrays inside [egress], not [[allow]] / [[deny]] tables.

Applying a change

These files are read at process start and not watched. Restart the daemon:

terminal
$ grith daemon stop && grith daemon start

There is no grith config reload - that command does not exist. If no persistent daemon is running, the next grith exec starts one that picks the change up.

Two that will surprise you

meta_rules.toml is required, not an optional extra - it loads on every start and a file that will not parse is a hard config error. Its five rules adjust the composite after phase 3, so they only reach calls that get that far: the ~/.ssh/id_rsa read above is already at 9.0 and denied at the end of phase 1, before the ssh-key-access override ever runs. See Meta-rules.

Half of domains.toml does nothing. Only the domains array inside [known_safe] and [known_malicious] is deserialised, so the patterns array (*.onion, *.bit, *.i2p) and the whole [suspicious] table are prose left in the file, not policy.

See also

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