I asked my coding agent to cat a file in –/.ssh. It did A test of GitHub Copilot CLI 1.0.105 on macOS with default settings found the agent read a canary file in ~/.ssh and listed the home directory with no approval prompt, and that its sandbox is off by default — the only startup dialog was a folder-trust question. The author, who built the npm tool envgit as version control for an agent's working environment, identified three gaps across Copilot CLI, Claude Code and Codex: sandboxes that are off or allow reads everywhere by default, approvals that collapse to "the rest of this session," and hostname-based network allowlists that cannot distinguish a `git push` POST to github.com from a `git fetch`. The author argues the third gap matters most for unattended agents because "a sandbox can give you back a file. It cannot give you back a push, a published package, or an issue opened on someone else's repo. Oct 28, 2026 I asked Copilot CLI whether it could read ~/.ssh . It said it had not checked and might be able to. I asked it to cat a file there. It did, with no approval prompt. ls ~ listed my home directory, also without a prompt. This was Copilot CLI 1.0.105 on macOS, a fresh directory, default settings, and a canary file in ~/.ssh in place of a real key. ~/.copilot held no saved approvals from earlier sessions. The only dialog in the whole session was the folder-trust question at startup, which says Copilot will run shell commands "with your permission" and "will remember your permissions for the rest of this session": With --sandbox it was a different program: ls ~ failed with Operation not permitted , from the sandbox, not from the model. So the sandbox works. It is off by default, and nothing in a default session tells you that. And on, its generated policy leaves outbound network open by default, as its own help text says. I went looking because of a friend. He approves every shell command his coding agent runs, one at a time, and had for months. Then Copilot updated and he stopped seeing the prompts: once he had allowed a shell command in a session, the rest went through. He looked for the sandbox boundary, could not find one, and sent me the screenshots with a one-line conclusion: "I want a sandbox the AI can play in." That is where envgit started. It is on npm now; this post is what I learned building it, including the parts that did not work. All three agents ship a local sandbox. Claude Code's is built on Anthropic's open-source sandbox-runtime Seatbelt on macOS, bubblewrap on Linux ; Codex uses Seatbelt and Landlock; Copilot CLI's, on Microsoft's MXC, went GA this month. They are real OS-level boundaries, and envgit uses one of them underneath. But they were designed around one accident, rm -rf , and the accidents that actually cost people are different ones. Three gaps, in the order the experiment above hit them: - Off, or open, by default. Copilot's sandbox is off unless you turn it on, and off means, in its own words, "the same access your user account has". Claude Code's and Codex's are on, but both allow reads everywhere unless you list what to hide; sandbox-runtime documents its read policy as deny-then-allow with nothing denied. ~/.ssh , ~/.aws , .env , browser profiles: readable until you say otherwise. A deleted file comes back from a backup. A leaked key does not. - Approval collapses to the session. Per-command prompts are the right idea and nobody survives them. A long task is hundreds of commands, so every UI ends up offering "for the rest of this session", and that is what people click. From then on there is no record of what was allowed, only that something was. In my reproduction there was not even a first prompt to collapse: the folder-trust dialog was the whole approval flow. - The dangerous things look like safe things on the wire. git push is a POST to github.com , the host every git fetch talks to. npm publish is a PUT to the registry you install from. A network allowlist by hostname cannot tell them apart, so either the host is allowed and the push goes, or it is blocked and nothing works. Copilot's generated sandbox policy sets outbound network to allow by default; Claude Code's and Codex's filter by host. None of the three look inside the request. The third gap is the one that matters once an agent runs unattended. A sandbox can give you back a file. It cannot give you back a push, a published package, or an issue opened on someone else's repo. Those are the actions that need a different treatment, and none of the sandboxes treat them differently. envgit is version control for the environment an agent works in. The analogy to git is literal enough to be useful: | git | envgit | |---|---| | commit | a snapshot after every step the agent takes | | log | what each step did: command, files written, requests made, what was refused | | reset | envgit rewind 7 puts the environment back to after step 7 | | branch | envgit fork 3 ../try-b materializes step 3 into a new directory | | — | irreversible actions are stopped at the request, or need a yes, before they leave | git was built for the way people change code: a few deliberate commits a day, each one a unit of intent, with the author there to write the message. An agent changes an environment hundreds of times an hour, and the unit that matters is not a commit but a step: this command, run under this policy, that touched these files and reached these hosts. git records none of that, by design. It cannot tell you that step 47 was a curl | sh , that step 60 was approved by a prompt someone clicked through, or that the working tree at step 12 had a passing test suite before the agent "cleaned up". For a human, the chat log fills that gap badly. For an unattended run, nothing fills it. envgit does not replace git; the demo below recovers a git commit. It versions the layer git was never asked to see. That last row is the part git has no equivalent for, and it is the part the sandboxes are missing. The policy is one YAML file per project. The interesting lines are the request-level ones: network: allow: "api.github.com:443", "registry.npmjs.org" l7: api.github.com: { methods: GET } irreversible: - preset: default git push, npm publish, PyPI, crates.io, image pushes, GitHub/GitLab API writes - net: { host: "api.github.com", method: POST, path: "/repos/ / /issues" } action: ask When a policy has request-level rules, the sandbox proxy terminates TLS with a per-run CA that is trusted only inside the sandbox, so every HTTPS request arrives with a method and a path. git push is POST /