Skip to content

Build1 publisher3 min readPublished

This agent-root gate refuses any root inside $HOME unless you pass --i-mean-it

A dev.to walkthrough reproduces a coding agent that followed curl --netrc out of a small repo and quoted homedir credentials into the chat. The author ships a CI gate for it and says plainly it is not a sandbox.

The Engineer · Build desk

Illustration accompanying This agent-root gate refuses any root inside $HOME unless you pass --i-mean-it

What happened

  • A coding agent opened at ~/work read a checkout script that called curl --netrc, followed the flag to ~/.netrc, and quoted the machine, login and password lines back into the chat.
  • The author reports no prompt injection and no CVE, presents the post as a lab walkthrough, and asks readers to treat the commands as a local regression rather than proof a hosted product leaked credentials.
  • Negative fixtures hold deliberately fake credentials, and the token canary-netrc-token-NOTREAL is the oracle for spotting the same text later in an export, a RAG chunk or a chat transcript.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The containment rule rejects the workspace layout the post says developers actually use, so adopting the gate means moving repos out of $HOME or living with an override that CI is supposed to refuse.
  • exposure Anyone who can read agent traces, session exports or the log shipper can read the credential lines after the fact, so retention sets the reach of one tool call.
  • decision A team has to pick which failure it is defending against, because the basename walk only sees secrets already copied into the workspace and the author says shell tools can still walk $HOME.

The gate runs two checks. Each catches a different failure. `iter_files` walks the agent root with `rglob` and flags any file whose basename is on the deny list [8][11]. In the incident the post describes, the credential sat at `~/.netrc` while the root was `~/work` [1]. A walk of `~/work` never reaches `~/.netrc`.

The other check does catch it. The gate refuses any root whose resolved path is inside `$HOME` unless the caller passes `--i-mean-it`, and the post says CI must not pass it [7]. `~/work` resolves inside `$HOME` by construction, so the layout the author says developers pick because "that is where the repos live" fails the gate on day one [22][23].

So the deny list is aimed at a different case: a credential file already sitting in the workspace. The author applies the same rule to logs. If the agent cannot open `.netrc`, the post says, it also cannot open a support zip that already copied `.netrc`, and the redaction belongs at the workspace edge, not at the prompt [20].

`iter_files` yields a path only if `p.is_file() and not p.is_symlink()` [11], so a symlink inside the repo pointing at `~/.netrc` is dropped by the walk before `denied()` ever sees the basename [12]. And the listing in the post breaks off inside `main()` at the line `home =` [13], so the comparison against `$HOME` itself is not shown.

The deny surface is small on purpose: eight basenames, including `.netrc`, `.pgpass`, `id_ed25519` and `kubeconfig` [8], plus three path tails, `.docker/config.json`, `.aws/credentials` and `.kube/config` [9]. Eleven patterns [10]. The author names `id_rsa`, `.git-credentials` and kube `user.token` files as further assets denied by name in the same gate [21], and then gives the limit himself: he does not treat a filename deny-list as a sandbox, he calls it a CI tripwire, and he says shell tools can walk `$HOME` unless you block that too [19].

That last sentence is the honest scope. A basename check inside the workspace does nothing about a tool loop that can shell out. The control that would have stopped last Tuesday is the root-containment rule. Its enforcement is a flag a developer can type on a laptop [7].

The leak oracle is a string. The negative `.netrc` fixture carries `canary-netrc-token-NOTREAL` [17], and the author's rule is that the string appearing in a later export, RAG chunk or chat transcript means the leak is in the pipeline: "Keep it ugly so grep never collides with a real password" [18].

For any of this to transfer you need two things to be true of your setup: secrets living under `$HOME`, and an agent file tool that resolves realpaths before opening. The post identifies that boundary as "which realpaths the file tool may open" [3]. The evidence here is one anecdote and code the author says is unexecuted until you run it, with the gate's exit code as the expected evidence [14]. He also states he did not find a CVE [4]. In my view the containment check is worth wiring into CI on its own, because it is stdlib Python and it fails closed; the deny list I would treat as detection for copied secrets.

What to watch

  • A complete listing of main(). It would show how the gate compares a resolved root against $HOME and how it handles paths that merely share a prefix.
  • Whether agent vendors ship a root-containment check by default. That would make the fixture's second rule redundant.
  • Any report of the canary string turning up in a vendor session export or trace store. That is the only evidence that the pipeline leak is real.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories