grithdocs

Taint tracking

What counts as a sensitive source, which of the three levels it gets, and what the two data-flow defaults changed.

grith does not read file contents, so it cannot follow the bytes of a credential through a program. What it does instead is record that a session touched a sensitive source, and score later outbound calls in the light of that.

That is the honest description of taint in grith: provenance at the path level, not dataflow at the byte level.

The three levels

LevelSourcesArms sticky containment
High.ssh, id_rsa, id_ed25519, private_key, shadowYes
Medium.env, .aws, credentials, secrets, .gnupg, .kube/configNo
LowAnything else matching a configured sourceNo

Medium deliberately does not arm sticky containment. Agent tools read their own credential files at startup, and locking a session down for that would make containment mean nothing.

The configured sources default to .env, .env.local, credentials.json, secrets.yaml, id_rsa and id_ed25519. Network and shell are high-risk sinks; file writes are medium-risk.

Scope and lifetime

Taint is registered against a scope key: the conversation id when there is one, otherwise the session's own uuid. There is no cross-session bleed - a taint registered in one session cannot influence scoring in another.

Registry entries live for one hour and are evicted on the next access.

What actually fires

Two defaults, both on, decide when taint contributes rather than just existing.

taint_data_flow_only = true. The old behaviour - any taint in the session adds 3.0 to every subsequent spawn and connect - is off. Taint now needs to be connected to the call, not merely present in the session.

taint_outbound_requires_data_flow = true. An outbound-capable binary running under taint - git push, aws s3 ls, npm publish - fires only when the spawn actually references the tainted data. Running git push in a session that read a .env file an hour ago is not, on its own, evidence of anything.

Together these are why a v0.3.1 session is much quieter than the same workload used to be, and they are the two settings to look at first if you expected taint to fire and it did not.

A matched spawn scores +3.0. Direct taint matches score 3.0, 4.0 or 5.0 by level, and the data-flow spawn variants score 1.5.

Where taint reaches beyond its own filter

Taint is filter 15, but two other things read it.

The env-exfiltration-risk meta-rule adds +5.0 when a taint sourced from an env file is followed by an HTTP request - the concrete shape of "read .env, post it somewhere". exfil-shape-under-session-taint adds +2.0 when an exfiltration-shaped outbound call happens while the session carries taint.

The windowed session-containment filter also arms on any call carrying taint, which is how a single sensitive read raises the price of network and shell for the following ten minutes.

What it catches, and what it does not

Catches: read a credential file, then try to reach a destination that is not already trusted. Read a key, then spawn something that can talk to the network with it. Read a secret, then open a shell.

Does not catch: a credential the model has memorised and retypes as a literal - there is no read to taint. A credential summarised into prose and posted as text; that is what dlp-gate and secret-scan are for. Data leaving through a channel grith does not trap at the syscall boundary.

Taint is one signal in a composite, and it is at its strongest in combination - taint plus an unknown destination plus a POST body is what turns a 3.0 into a denial.

See also

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