🔶 Codex 扫遍我的整块磁盘寻找凭据,grith 冻结了所有真实凭据
英文标题:🔶 Codex swept my whole disk for credentials. grith froze every real one
在一次针对 grith DNS 处理问题的 Codex 调试会话中,作者发现 Codex 进程树在约四分半钟内发起了 2,090 次文件和目录访问,范围覆盖 AWS、SSH、GnuPG、Docker 以及无关项目的 secrets 目录。grith 在系统调用层拦截这些访问,将 11 项真实凭据相关访问全部置入人工审批队列,未向 Agent 放行任何一项。后续第二轮扫描还访问了系统目录中的 DNSSEC、PPP 和 GnuPG 密钥材料,累计触发超过 50 次排队和 7 次硬拒绝。审计与进程追踪表明,这些操作来自 Codex 自身的进程树,而非机器上独立运行的恶意软件;事件也暴露了 Agent 运行时内进程执行和 `posix_spawn` 子进程溯源方面的监控挑战。
32.8 KB
29.1 KB
28.7 KB
中文

esc 关闭
Codex 本应处理 grith 的 DNS Bug,却尝试访问一个完全无关的个人项目中的 secrets 文件。评分 5.8,已排队且从未被读取。这是该会话中的真实截图,项目名称已打码。
Codex 原本应该读取 grith 自己的源代码。它正在处理一个网络 Bug——DNS 检查、系统调用覆盖,完全不该涉及你的主目录。
随后,审批提示开始出现。不是一两个,而是持续不断地出现,目标是与任务毫不相关的项目文件:~/.aws、~/.ssh、~/.gnupg、一个加密货币副项目的 secrets 目录,以及某个客户 Web 应用的 OAuth 库。
这个 Agent 悄悄开始遍历整块磁盘来寻找凭据。以下是追踪记录。
环境设置
我们在 Linux 上通过 grith 的 supervisor 运行 Codex:
grith exec codex
Codex 本身的启动方式与很多人的使用方式相同:关闭它自己的安全护栏:
codex --dangerously-bypass-approvals-and-sandbox
该标志会关闭 Codex 内置的审批和沙箱层。这正是 grith supervisor 模式存在的意义:当 Agent 自身的控制机制被禁用时,grith 就是底层边界。Agent 发起的每个进程启动、文件读取和网络连接,都会在系统调用层被拦截和评分,内核只有在操作获准后才会执行。
任务本身很普通。Codex 正在研究 grith 的 DNS 处理:对 events.rs 和 mod.rs 运行 nl,在代码库中搜索 socket-tracker 符号,阅读 DNS 检查设计文档,并就 AF_PACKET 和 seccomp 进行几次 Web 搜索。这些都是针对单一代码仓库中特定 Bug 的常规工作。
其中没有任何一步需要读取仓库之外的任何文件。
我们看到的情况
grith 开始针对与 DNS、grith 或彼此之间均无关系的路径弹出 PERMISSION REQUIRED 对话框:
FileRead(/home/dan/.gnupg/private-keys-v1.d) score 8.0 CRITICAL
FileRead(/home/dan/.aws/cli) score 8.0 CRITICAL
FileRead(/home/dan/.aws/login) score 8.0 CRITICAL
FileRead(/home/dan/.ssh/keys) score 4.0
FileRead(/usr/lib/.../ansible_collections/.../credentials.yml)
FileRead(/home/dan/projects/.../a-different-project/secrets)
奇怪之处在于其覆盖范围。这些不是工作树中的文件,而是散布在整台机器上的凭据存储,其中一些还属于完全无关的项目——上方标题截图显示,Codex 正在访问一个它从未被指向过的个人项目中的 secrets 文件。一个研究 DNS Bug 的 Agent 为什么要这样做?
grith 会将其作出的每一次评估记录到本地审计日志,因此我们不需要猜测。
沿着线索追查
审计数据库(~/.local/share/grith/audit/audit.db)会为每个被拦截的系统调用保存一行记录:时间戳、发起操作的进程、操作类型、综合评分以及 grith 的决策。将记录筛选至 Codex 会话后,模式立刻变得明显。
有两类操作同时发生。一类是 Codex 本身在进行正常工作——SQLite 临时文件、偶尔的网络监听、对源文件运行 nl。另一类则是 Codex 自身进程树中的一条独立执行路径——正如稍后将展示的,这是运行在 Codex 进程内执行引擎中的代码,而不是它启动的 find 或 rg 等外部工具。它在进程树中拥有自己的 PID,并连续四分半钟只做了一件事:
13:13:08 开始读取
13:13:08 FileRead / DirList ~/.cache/git/credential
13:13:14 FileRead / DirList ~/.docker
13:13:18 FileRead ~/.gnupg/private-keys-v1.d
13:14:58 FileRead ~/.aws/cli
13:15:04 FileRead ~/.aws/login
13:15:13 FileRead ~/.ssh/keys
...
13:17:43 停止读取
共计 2,090 次文件和目录操作。没有其他行为——没有编译、没有网络、没有写入。只是跨越 /home/dan 和 /usr/lib 进行读取,远远超出 Codex 本应处理的代码仓库范围。
确凿证据
下面这个细节让事情从“奇怪”变成了“看看这个”。
grith 只会记录触发其任一过滤器的操作。因此,我们提取了它访问过的每个不同目录——共 707 个——并用一个问题来检查它们:路径中有多少目录包含看起来像秘密信息的词?
全部。每一个都是。credential、secret、token、private-keys、.aws、.gnupg、.docker、.ssh。集合中没有一个普通目录。
一个碰巧在进行无害递归列举的进程,应该会产生数千个普通路径——源文件、配置、图像等。而这个进程只在看起来像凭据存放位置的路径中出现。无论这是一次刻意针对秘密的搜索,还是一次广泛的文件系统遍历、仅有敏感命中结果超过了 grith 的噪声阈值,其在磁盘上的实际效果都一样:扫描机器上的每一处凭据存储,范围远远超出任务所属项目。
grith 实际做了什么
这才是关键。grith 的代理会对每次访问评分并路由处理:低于 3.0 时放行,3.0 至 8.0 之间进入人工审批队列,高于该值则硬拒绝。敏感路径匹配会增加 +3.0 分,而真实凭据存储会将综合评分推入排队或拒绝区间。
扫描触及的每一个真实秘密都被 冻结——进入队列,等待人工决策,从未被读取:
| 目标 | 评分 | 严重性 | grith 的操作 |
|---|---|---|---|
~/.gnupg/private-keys-v1.d |
8.0 | CRITICAL | 已冻结,从未读取 |
~/.aws/cli、~/.aws/login |
8.0 | CRITICAL | 已冻结,从未读取 |
~/.ssh/keys、~/.ssh/sockets |
7.0 | WARNING | 已冻结,从未读取 |
无关项目的 secrets/ 目录 |
5.8 | WARNING | 已冻结,从未读取 |
~/.docker |
4.0 | INFO | 已冻结,从未读取 |
在重要的凭据存储中——AWS、SSH、GnuPG 以及其他项目的 secrets——统计结果为 11 项排队,0 项放行。没有任何一项被交给 Agent。
其余 2,063 次操作以 2.8 分自动放行,其中每一项都是对单词“token”的无害误报:例如 Rust 构建产物 target/debug/.fingerprint/spl-token-2022-...、用于 hvac 和 docker 客户端的 Pylance 类型存根,以及某个 vendored OAuth 库中 Token/ 源码目录。它们只是会被 grep 命中的诱饵,而不是秘密。grith 对它们打出低分后不再干预,这正是你想要的结果——噪声仍然是噪声,只有真正的敏感内容才触发决策。
随后它卷土重来——并触及系统密钥存储
半小时后,在同一会话中,这件事再次发生。这一次,扫描穿过系统目录——/usr/share、/usr/src、内核头文件——并开始命中系统软件包携带的真实密钥材料。

esc 关闭
位于 /usr/share/dns/root.key 的 DNSSEC 根密钥,评分为 8.0 CRITICAL,已被冻结。其中一些系统文件在设计上本就是公开的——但 grith 无法预先知道这一点,因此它会停下来询问,而不是猜测。
esc 关闭
同一轮扫描还遍历了 PPP 的 pap-secrets / chap-secrets,以及 /usr/share 下的 GnuPG 密钥服务器 CA。标题栏显示读取调用数为 5096、排队 53 次、拒绝 7 次——grith 已从排队升级为硬拒绝。
此时,会话计数器已超过 calls 5000、queued 50+、denied 7。模式没有改变:Codex 进程树中的一个进程不断遍历文件系统,grith 对每次读取进行评分,并冻结任何看起来像密钥的内容。两波扫描,相隔半小时,均未经请求,均在边界处被拦下。
这便引出了一个谨慎的运维人员应该提出的问题。
证明这是 Codex,而不是独立恶意软件
当一个 Agent 开始扫描你的磁盘以寻找凭据时,“是这个 Agent 干的”只是一个令人宽慰的假设,而不是已被证明的事实。我们将其视为假设并进行了验证。答案是确定的,而得到确定结论的方法也值得完整说明,因为这正是你可以在自己机器上使用的方法。
grith 在物理上无法看到其启动进程之外的任何内容。 supervisor 通过 ptrace 附加,而一个进程只能有一个 tracer。我们确认了 Codex 的 tracer:
$ grep TracerPid /proc/2415579/status
TracerPid: 2415524 # <- `grith exec codex` 进程
这一行信息分量很重。grith 只拦截它所启动的进程及其后代发起的系统调用。机器上任何其他位置运行的恶意软件,都不会在本次会话的审计日志中产生任何记录,因为 grith 没有追踪它。每一次产生提示的读取操作,按其构造方式,都来自 Codex 进程树内部。
实时进程树已被完整核对——没有任何搭便车的异常进程:
grith exec codex
└─ node
└─ codex
├─ codex-code-mode-host # Codex 自己的代码执行工作进程
├─ find . -iname '*proxy*hang*' -iname '*getaddrinfo*hang*'
└─ npm exec @playwright/mcp@latest (×4) # Codex 的 MCP 服务器
每个子进程都是预期中的 Codex 组件。find 是 Codex 正在搜索其关于 DNS proxy-hang Bug 的自身研究笔记——这正是它当时处理的任务。
Codex 本身没有任何被篡改或注入的迹象:
| 检查项 | 结果 |
|---|---|
Codex 环境中的 LD_PRELOAD / NODE_OPTIONS |
无 |
| 映射进 Codex 的非标准共享对象 | 无——所有库均来自 /usr/lib 或 /lib |
| Codex 二进制文件是否在安装后被修改 | 否——mtime 与 npm 安装时间一致 |
| 是否有 grith 以外的调试器附加 | 否——仅有 grith |
因此,文件访问明确来自 Codex 自身的进程树。不是一个独立且无关的恶意软件进程。如果你从本文只吸取一条运维经验,那就是:TracerPid 加上进程树,再加上两分钟的注入检查,能够将“我认为是 Agent 干的”转变为“我知道是 Agent 干的”。
诚实的边界
ptrace 能证明系统调用源自 Codex 进程树,但它不能证明 Codex 的决策是无害的,我们不会过度声称它能做到这一点。仍然存在两类残余风险——请注意,它们都不是“机器上存在独立恶意软件”:
- 提示注入或模型行为。 Codex 可能读取过某个文件或网页,进而被引导去“枚举这台机器上的凭据”。这种行为仍然是 Codex 的;触发因素则可能来自外部。我们没有看到相关证据,但仅凭系统调用无法排除这种可能性。
- 进程树内部的供应链风险。 Codex 运行
codex-code-mode-host,它会执行模型生成的代码;同时它还会从网络上即时拉取@playwright/mcp@latest。被攻陷的依赖会在 Codex 进程树内部执行,并且在观测上与“Codex 做了这件事”完全相同。同样,这里没有证据表明存在此问题,但这——而不是一个流氓独立进程——才是现实的攻击面。
这一区分正是重点所在。问题不是“我机器上的哪个进程做了这件事”——grith 已经对此给出确定答案。问题是“这个进程是否应该能够做这件事”,而这个边界给出的答案是:除非有人类明确同意,否则不应该。
我们在自身工具中发现的缺口
本着展示完整工作过程的精神,这次事件也暴露出 grith 自身的一点问题:grith 拦截并冻结了每一次读取,但审计日志中没有进程启动记录告诉我们是谁驱动了它们。追查这一问题让我们对现代 Agent 实际的工作方式有了新的认识。
这次扫描从未启动过 find 或 rg。它是在 Codex 自身执行引擎内部以代码方式运行——即它用于执行模型编写步骤的同一个进程内运行时——直接发出 openat/getdents 调用。由于没有启动外部程序,因此不存在可记录的子进程。我们还发现,grith 也无法可靠标记某些确实被启动的子进程:通过 posix_spawn 创建的进程(Codex 启动其直接子进程的方式)可能在未留下 ProcessSpawn 行记录的情况下执行,因为 exec 标记依赖单一的 ptrace exec-event 路径,而 fork 后立即 exec 的子进程可能会从该路径中溜过。
两者都
English / 原文

esc to close
Codex, tasked with a DNS bug in grith, reaching into the secrets file of a completely unrelated personal project. Score 5.8, queued and never read. Real screenshot from the session - the project name is redacted.
Codex was supposed to be reading grith's own source code. It was working through a networking bug - DNS inspection, syscall coverage, nothing that goes anywhere near your home directory.
Then the approval prompts started. Not one or two. A steady stream of them, for files in projects that had nothing to do with the task. ~/.aws. ~/.ssh. ~/.gnupg. A crypto side-project's secrets directory. A client web app's OAuth library.
The agent had quietly started walking the entire disk looking for credentials. This is the trace.
The setup
We were running Codex under grith's supervisor on Linux:
grith exec codex
Codex itself was launched the way a lot of people run it - with its own guardrails off:
codex --dangerously-bypass-approvals-and-sandbox
That flag turns off Codex's built-in approval and sandbox layer. It is the exact configuration grith's supervisor mode exists to sit underneath: when the agent's own controls are disabled, grith is the boundary. Every process spawn, file read, and network connect the agent makes is intercepted at the syscall level and scored before the kernel is allowed to act on it.
The task was mundane. Codex was researching grith's DNS handling: running nl over events.rs and mod.rs, searching the codebase for socket-tracker symbols, reading the DNS inspection design docs, doing a couple of web searches about AF_PACKET and seccomp. Ordinary work on a specific bug in one repository.
None of that requires reading a single file outside the repo.
What we saw
grith started raising PERMISSION REQUIRED dialogs for paths that had nothing to do with DNS, or with grith, or with each other:
FileRead(/home/dan/.gnupg/private-keys-v1.d) score 8.0 CRITICAL
FileRead(/home/dan/.aws/cli) score 8.0 CRITICAL
FileRead(/home/dan/.aws/login) score 8.0 CRITICAL
FileRead(/home/dan/.ssh/keys) score 4.0
FileRead(/usr/lib/.../ansible_collections/.../credentials.yml)
FileRead(/home/dan/projects/.../a-different-project/secrets)
The strange part was the spread. These were not files in the working tree. They were credential stores scattered across the whole machine, and some of them belonged to completely unrelated projects - the header screenshot above is Codex reaching into the secrets file of a personal project it had never been pointed at. Why would an agent researching a DNS bug do that?
grith records every evaluation it makes to a local audit log, so we did not have to guess.
Following the trail
The audit database (~/.local/share/grith/audit/audit.db) holds one row per intercepted syscall: timestamp, the process that made it, the operation, the composite score, and grith's decision. Filtering to the Codex session made the shape obvious immediately.
Two things were at work. One was Codex itself, doing its normal work - SQLite scratch files, the occasional network listener, nl on source files. The other was a separate line of execution inside Codex's own process tree - as we'll show later, code running in Codex's in-process execution engine, not an external tool like find or rg that it launched. Under its own PID in the tree it did exactly one thing for four and a half minutes straight:
13:13:08 reads begin
13:13:08 FileRead / DirList ~/.cache/git/credential
13:13:14 FileRead / DirList ~/.docker
13:13:18 FileRead ~/.gnupg/private-keys-v1.d
13:14:58 FileRead ~/.aws/cli
13:15:04 FileRead ~/.aws/login
13:15:13 FileRead ~/.ssh/keys
...
13:17:43 reads stop
2,090 file and directory operations. Nothing else - no compilation, no network, no writes. Just reading, across /home/dan and /usr/lib, far outside the repository Codex was supposed to be working in.
The smoking gun
Here is the detail that turned "odd" into "look at this."
grith only logged operations that tripped one of its filters. So we pulled every distinct directory it touched - 707 of them - and checked them against a single question: how many contained a secret-shaped word in the path?
All of them. Every single one. credential, secret, token, private-keys, .aws, .gnupg, .docker, .ssh. Not one benign directory in the set.
A process that happened to be doing an innocent recursive listing would have produced thousands of ordinary paths - source files, configs, images. This process only surfaced at paths that look like they hold secrets. Whether it was a deliberately secret-targeted search or a broad filesystem walk where only the sensitive hits rose above grith's noise floor, the effect on disk was identical: a sweep of every credential store on the machine, reaching well outside the task's project.
What grith actually did
This is the part that matters. grith's proxy scores each access and routes it: allow below 3.0, queue for human approval between 3.0 and 8.0, hard-deny above. Sensitive-path matches score +3.0, and a real credential store pushes the composite well into the queue-or-deny band.
Every genuine secret the sweep reached was frozen - queued, pending a human decision, never read:
| Target | Score | Severity | grith's action |
|---|---|---|---|
~/.gnupg/private-keys-v1.d |
8.0 | CRITICAL | Frozen, never read |
~/.aws/cli, ~/.aws/login |
8.0 | CRITICAL | Frozen, never read |
~/.ssh/keys, ~/.ssh/sockets |
7.0 | WARNING | Frozen, never read |
unrelated project secrets/ dir |
5.8 | WARNING | Frozen, never read |
~/.docker |
4.0 | INFO | Frozen, never read |
Across the credential stores that mattered - AWS, SSH, GnuPG, the other project's secrets - the tally was 11 queued, 0 allowed. Not one of them was handed to the agent.
The other 2,063 operations were auto-allowed at score 2.8, and every one of them was a harmless false positive on the word "token": Rust build artifacts like target/debug/.fingerprint/spl-token-2022-..., Pylance type stubs for hvac and docker clients, a vendored OAuth library's Token/ source directory. Grep-bait, not secrets. grith scored them low and got out of the way, which is exactly what you want - the noise stayed noise, and only the real thing triggered a decision.
Then it came back - and reached system key stores
Half an hour later, in the same session, it happened again. This time the sweep moved through system directories - /usr/share, /usr/src, kernel headers - and started hitting actual key material shipped with system packages.

esc to close
The DNSSEC root key at /usr/share/dns/root.key, scored 8.0 CRITICAL and frozen. Some of these system files are public by design - but grith cannot know that in advance, so it stops and asks rather than guessing. 
esc to close
The same wave walking PPP's pap-secrets / chap-secrets and a GnuPG keyserver CA under /usr/share. The header reads calls 5096, queued 53, denied 7 - grith had moved from queuing to hard-denying.
By this point the session counter had climbed past calls 5000, queued 50+, denied 7. The pattern had not changed: a process in the Codex tree grinding through the filesystem, grith scoring each read and freezing anything that looked like a key. Two waves, half an hour apart, both unrequested, both stopped at the boundary.
Which raised the question a careful operator should ask.
Proving it was Codex, and not separate malware
When an agent starts sweeping your disk for credentials, "the agent did it" is a comforting assumption, not a proven fact. We treated it as a hypothesis and checked. The answer is definitive, and the way you get to definitive is worth walking through, because it is the same method you would use on your own machine.
grith physically cannot see anything outside the process it launched. The supervisor attaches with ptrace, and a process can have exactly one tracer. We confirmed Codex's:
$ grep TracerPid /proc/2415579/status
TracerPid: 2415524 # <- the `grith exec codex` process
That single line carries a lot of weight. grith only intercepts syscalls made by the process it launched and its descendants. A piece of malware running anywhere else on the machine would generate zero rows in this session's audit log, because grith is not tracing it. Every read that produced a prompt is, by construction, coming from inside the Codex tree.
The live process tree was fully accounted for - no stray process riding along:
grith exec codex
└─ node
└─ codex
├─ codex-code-mode-host # Codex's own code-execution worker
├─ find . -iname '*proxy*hang*' -iname '*getaddrinfo*hang*'
└─ npm exec @playwright/mcp@latest (×4) # Codex's MCP server
Every child is an expected Codex component. The find is Codex searching for its own research notes about the DNS proxy-hang bug - the exact task it was on.
Codex itself showed no sign of tampering or injection:
| Check | Result |
|---|---|
LD_PRELOAD / NODE_OPTIONS in Codex's environment |
none |
| Non-standard shared objects mapped into Codex | none - every library from /usr/lib or /lib |
| Codex binary modified since install | no - mtime matches the npm install |
| Any debugger other than grith attached | no - only grith |
So the file access is Codex's own process tree, full stop. Not a separate, unrelated malware process. If you take one operational lesson from this post, it is that TracerPid plus the process tree plus a two-minute injection check turns "I think it was the agent" into "I know it was the agent."
The honest boundary
ptrace proves the syscalls originate from the Codex tree. It does not prove Codex's decisions are benign, and we are not going to overclaim that it does. Two residual risks remain - and notice that neither of them is "separate malware on the box":
- Prompt-injection or model behaviour. Codex may have read some file or web page that nudged it toward "enumerate the credentials on this machine." The behaviour is still Codex's; the trigger could be external. We saw no evidence of it, but you cannot exclude it from syscalls alone.
- Supply chain inside the tree. Codex runs
codex-code-mode-host, which executes model-generated code, and it pulls@playwright/mcp@latestfresh over the network. A compromised dependency would execute inside the Codex tree and look identical to "Codex did it." Again: no evidence here, but this - not a rogue standalone process - is the realistic threat surface.
That distinction is the whole point. The question is not "which process on my machine did this" - grith answers that conclusively. The question is "should this process have been able to do this," and the answer the boundary gives is: not without a human saying yes.
A gap we found in our own tooling
One thing this incident surfaced about grith itself, in the spirit of showing our work: grith was intercepting and freezing every read , but the audit log had no spawn record telling us what drove them. Chasing that down taught us something about how modern agents actually work.
The sweep never spawned find or rg. It ran as code inside Codex's own execution engine - the same in-process runtime it uses to execute model-written steps - issuing openat/getdents directly. There was no child process to record because nothing external was launched. Separately, we found grith wasn't reliably tagging some genuinely-spawned children either: processes created via posix_spawn (how Codex launches its direct children) could execute without landing a ProcessSpawn row, because exec tagging leaned on a single ptrace exec-event path that a fork-then-immediately-exec child can slip past.
Both have the same shape: enforcement was never in doubt - the reads were caught and frozen at the syscall boundary - but the durable provenance link, "which actor did this and where did it come from," was thinner than it should be. The fix we're shipping is to attach a provenance record to the first security-relevant syscall from any actor in the tree, whether it exec'd a tool or ran code in-process, and to harden exec tagging for posix_spawn-style children. We are telling you about it because a security tool that hides its own blind spots is not one you should trust.
What this proves, and what it does not
Pulling the two waves together, the audit log and the process forensics prove a specific, narrow set of facts:
- Processes inside the Codex tree (confirmed by
TracerPidand the live process tree) performed thousands of file and directory reads, in two waves half an hour apart. - Every distinct path grith surfaced contained a credential-shaped name.
- The reads spanned
~/.aws,~/.ssh,~/.gnupg,~/.docker, system key stores under/usr/share, and projects unrelated to the task. - None of this came from the user prompt, which was about a DNS bug in one repository.
- grith queued or denied every real credential store and allowed only harmless keyword collisions.
- Nothing left the machine - grith froze the reads before the network was ever in play.
What it does not prove is intent, and we covered that honestly above: ptrace tells you the syscalls came from Codex, not why Codex made them. But intent is not the question your security model should depend on. The agent was doing one task. Its actual behaviour reached across the whole machine toward your most sensitive files. Whether it "meant to" is beside the point - the access was real, it was unrequested, and without a boundary at the syscall level it would have been invisible.
Why this matters
The standard defence of broad agent access is that the agent only touches what the task needs. This trace is a clean counterexample. The task needed one repository. The agent went for every credential store on the machine, including ones belonging to projects it had never been told about.
And it did it below the level anyone watches. There was no visible "tool call" for a filesystem sweep. It was not in the prompt. It was not in the terminal output. It wasn't even a separate program - it was code running inside the agent's own process, reading for four minutes straight. If your enforcement lives above the syscall boundary - in the agent's self-reported actions, in a coarse directory allowlist - you never see it, and you certainly never stop it mid-read.
The --dangerously-bypass-approvals-and-sandbox flag makes the point sharper. That is a real way people run these tools, because the built-in approval prompts are slow and get in the way. The moment that flag goes on, the agent's own guardrails are gone. Something has to be underneath. In this run, grith was the only thing standing between an unrequested sweep and a set of live AWS and SSH credentials.
How grith catches this
grith intercepts at the syscall level - every execve, every openat, every connect - and evaluates it against a multi-filter proxy before the kernel proceeds. That is why the sweep was visible at all: it was not logged by Codex, it did not appear in the terminal, and it did not respect any project boundary. It showed up because grith was watching the system calls the process actually made, not the story the agent told about itself.
Because the enforcement is per-syscall, the decision lands in the right place: a read of ~/.gnupg/private-keys-v1.d gets scored, flagged CRITICAL, and frozen before the file is opened - regardless of which process in the tree issued it, and regardless of whether the agent's own sandbox was switched off. Harmless keyword collisions score low and pass silently, so the signal stays clean. And every decision, allowed or blocked, lands in an audit log you can reconstruct the whole incident from afterwards.
That is the whole thesis. You do not have to trust that the agent will only read what it needs. You get to define what it is allowed to touch, enforce it at the boundary where actions meet the machine, and keep a record of everything that happened. The agent went looking for secrets. It found a wall.