Claude Code Attempted 752 /proc/*/environ Reads. 256 Succeeded. Codex: 0.

esc to closeWe asked Claude Code to add input validation to a single route handler. The task required editing one file. Roughly 20 lines of code.
Here is what happened before it wrote a single character.
The setup
We built a standardised benchmark: a small Node.js/Express user management service with a POST /users endpoint that was deliberately missing input validation. The task was identical for both agents: add validation for name, email, and password, return 400 with a descriptive error for each failure case.
We ran each agent under strace -f, recording every openat, connect, execve, and getdents64 call made by the agent process and every subprocess it spawned1. All runs used a fresh copy of the project. We ran Claude Code twice to check stability; the results were within 0.5% across both runs.
Agents tested: Claude Code 2.1.72 and Codex CLI 0.113.02.
The benchmark project and analysis scripts are published on GitHub if you want to run this yourself.
The numbers
| Claude Code | Codex | |
|---|---|---|
| Task duration | 17.9s | 42.2s |
| Files read (unique) | 2,779 | 303 |
| Files written | 10 | 20 |
| Files in the project | 7 | 4 |
| Files outside the project | 2,782 | 319 |
| Directory scans | 368 | 750 |
| Unique subprocesses spawned | 14 | 25 |
Claude Code opened 2,779 unique files in the process of editing one. Codex opened 303.
Before interpreting the gap: most of Claude Code's reads are its own infrastructure - plugin cache, config files, node module resolution. The absolute number is not the finding. What the agent read is.
Finding 1: Claude Code scanned the environment of 752 running processes
During the session, Claude Code attempted to open /proc/<pid>/environ for 752 distinct process IDs - every process visible in /proc, from PID 1 upward. Most kernel and root-owned processes returned EACCES, but 256 succeeded - every process running under the current user. Across two runs, the numbers were 752/756 attempted and 256/254 successful. It did not vary meaningfully because it is not random. Claude Code walks /proc systematically on startup to inherit the parent shell's environment variables.
The mechanism is legitimate. Shells export environment variables - PATH, NODE_ENV, API keys you have exported in your .zshrc - and a terminal-launched process cannot read them any other way on Linux. So Claude Code walks /proc to find them.
But it does not read just the parent shell's environment. It reads the environment of every user-owned process it can access. On the test machine, the 256 successfully read processes included:
| Process | Instances | What it is |
|---|---|---|
| Firefox | 56 | Browser (tabs, extensions, GPU process) |
| VS Code | 15 | Editor and extension hosts |
| Zoom | 13 | Video conferencing (webview hosts) |
| Slack | 6 | Messaging |
| Claude Code | 7 | Other Claude sessions |
| Codex | 4 | Other Codex sessions |
| gnome-keyring-daemon | 3 | System credential store |
| bash / tmux | 27 | Terminal sessions |
| node | 4 | Node.js processes |
| DBeaver (Java) | 1 | Database client |
The gnome-keyring-daemon entry is worth highlighting. This is the process that manages your system keychain - SSH keys, GPG keys, Wi-Fi passwords, and any secret stored via the GNOME keyring API. Claude Code successfully opened its /proc/<pid>/environ, which contains the DBUS_SESSION_BUS_ADDRESS and other variables needed to interact with the keyring over D-Bus. Reading the environ does not extract stored secrets directly, but it provides the addressing information an attacker would need to query the keyring programmatically.
It also read /proc/<pid>/stat, /proc/<pid>/status, and /proc/<pid>/cmdline for processes starting from PID 1 - process names, states, and full command lines for the entire running process table.
Codex did not do any of this. Zero /proc/*/environ reads in either run.
Finding 2: Both agents read credentials the task never required
Neither agent needed any of the following to add input validation to a route. Both read them anyway.
| Path | Claude Code | Codex | Why |
|---|---|---|---|
~/.gitconfig | x49 | x47 | Each of the 16-18 git subprocesses resolves the global config independently |
/etc/passwd | x20 | x16 | Every subprocess resolves the current user via getpwuid() on startup |
~/.npmrc | x1 | x1 | npm registry auth token |
~/.ssh/config | x2 | - | SSH config, read during git remote operations |
~/.ssh/known_hosts | x4 | - | SSH host verification |
The ~/.gitconfig and /etc/passwd counts are not targeted reads - they are the noise floor of running Node.js and git in a subprocess tree. Each of the 14-18 subprocesses both agents spawned does its own user resolution. You would see similar numbers from a shell script that called git 16 times.
The ~/.npmrc read is more interesting. The task involved no npm publishing. The file contains a registry authentication token. It was opened because npm was invoked once to check the project's dependency state, and npm reads its config unconditionally on startup. Neither agent needed the token. Both opened the file that contains it.
Finding 3: Claude Code initialised Gmail and Google Calendar for a coding task
After the session completed, we found two log files written outside the project:
~/.cache/claude-cli-nodejs/.../mcp-logs-claude-ai-Gmail/2026-03-10T14-28-34-414Z.jsonl
~/.cache/claude-cli-nodejs/.../mcp-logs-claude-ai-Google-Calendar/2026-03-10T14-28-34-414Z.jsonl
Claude Code initialises every configured MCP server at session startup, before the task begins. The machine running this test had Gmail and Google Calendar MCP servers configured. Both were touched during a session whose only purpose was to add three if-statements to a route file.
The network trace corroborates this: one of Claude Code's outbound connections was to a Google Cloud IP (137.66.149.34.bc.googleusercontent.com:443), which does not appear in Codex's trace.
MCP servers can have significant access - reading your email, querying your calendar, accessing authenticated services. Whether or not they were actually used in this session, they were initialised. The attack surface of your coding session includes whatever MCP servers you have configured, on every session, regardless of the task.
Finding 4: Claude Code walked up to the parent git repository
The benchmark project was a subdirectory of a larger git repository. Claude Code found and read the parent project's .git/HEAD and .git/config.
This is standard git behaviour - git walks up the directory tree to find the nearest .git directory. But it means that in a real working environment where the project lives inside a monorepo or a larger workspace, the agent is reading git metadata - remote URLs, credentials embedded in remote configs, custom hooks - from repositories above the project it was asked to work on.
Finding 5: Claude Code ran a background plugin sync during the task
During the session, Claude Code acquired git locks and wrote ORIG_HEAD.lock files to two plugin marketplace repositories in ~/.claude/plugins/marketplaces/. It was pulling updates to its plugin registry while completing your task. This accounts for two of the three outbound network connections made during the session.
Both extra network round trips were for Claude's own maintenance, not the task.
Finding 6: Codex sources a full login shell before every command
Where Claude Code's distinctive behaviour is in the /proc scan, Codex's is in how it initialises each sandbox shell.
Before executing any command, Codex reads and sources:
/etc/profile- Every script in
/etc/profile.d/(bash_completion, locale, flatpak, debuginfod, apps-bin-path, and others)
A full login shell inherits everything configured at the system and user level - including any API keys or tokens you have exported in your profile scripts. This is why Codex spawned flatpak, lsb_release (five times), getopt, getconf, tr, cut, and wc - not as deliberate tool calls, but as side effects of sourcing profile scripts.
Claude Code does not source profile scripts. It inherits the parent shell's environment via the /proc scan instead. Two different mechanisms for the same goal - picking up your environment - with different side effects.
Finding 7: Codex connects on port 65535
Both of Codex's API connections used port 65535 - the highest valid TCP port number - rather than the standard HTTPS port 443. The destination IPs (104.18.32.47 and 172.64.155.209) are Cloudflare ranges, so these are routing to OpenAI's API via Cloudflare, but on a non-standard port.
This is worth noting for anyone running agent workloads in environments with restrictive egress rules. A firewall policy that allows only port 443 outbound will silently break Codex. More broadly, it means traffic analysis tools keyed to port 443 as the signal for "HTTPS to external services" will miss Codex's API calls entirely.
What this is not
None of the above is malicious. Claude Code is not stealing your secrets. Codex is not trying to fingerprint your system.
The /proc scan is a reasonable engineering choice for environment variable inheritance. The ~/.npmrc read is an unavoidable side effect of invoking npm. The /etc/passwd reads are standard Linux user resolution that every process on the machine performs. Sourcing /etc/profile.d/ is how login shells work.
The problem is not intent. The problem is visibility and scope.
The blast radius problem
When you ask an AI coding agent to edit a file, you are implicitly authorising a surface area you have never seen and cannot predict. In the 17.9 seconds it took Claude Code to add input validation to a route, it had:
- Scanned the environment variables of 752 processes (successfully reading 256, including your browser, Slack, credential store, and other terminal sessions)
- Opened credential files for services unrelated to the task
- Initialised MCP servers with access to your email and calendar
- Made network connections to infrastructure outside the project
- Touched git metadata from repositories above the project
None of this was requested. All of it happened.
The individual operations are defensible in isolation. The composition - everything that happens in a single session, across every session you run - is the exposure. In a compromised context (a poisoned file, a malicious tool description, a prompt injection in a comment the agent reads while understanding the codebase), any of these accesses becomes the attack surface.
This is the same structural problem we identified in MCP server poisoning and the Clinejection attack chain. The agent has access. Something causes the agent to act. The question is whether anything evaluates what the agent does with that access before it happens.
The visibility gap
Both agents provide some transparency at the tool-call level - you can see "Claude read users.js" and "Codex ran sed". Neither provides visibility at the operation level. You do not see the 752 /proc attempts, the ~/.npmrc open, the MCP server initialisation, or the parent git config access. These happen below the abstraction layer the agents expose.
This is not a criticism of the agents. It is a structural property of how they are built. The tool-call interface is designed to show you what the agent did intentionally. The syscall layer is where you see everything else.
Per-syscall interception is the layer that closes this gap. grith sits between the agent process and the kernel, scoring every operation before it executes - not what the agent claims to be doing, but what it actually does. The process census, the credential file opens, the MCP initialisation, the network connections: all of it is visible, all of it is scored, and any of it can be blocked by policy without stopping the agent from completing legitimate work.
If you want to see this in a real session - including what grith's scoring output looks like against the trace data above - join the waitlist for early access.
Methodology
All traces were run on Ubuntu 24.04, kernel 6.17.0. strace 6.8 was used with the following flags:
strace -f \
-e trace=openat,open,connect,execve,getdents64 \
-e signal=none \
-ttt -s 512 \
-o raw.log \
-- <agent-command>
The -f flag follows all forks and threads, capturing the full subprocess tree. Syscalls with a negative return value (failed operations) are excluded from counts. Agent runtime files (the agent's own node_modules and installation paths) are excluded from file counts but not from sensitive path detection.
The benchmark project is a minimal Node.js/Express service. The task - "add input validation to the POST /users endpoint" - was identical for both agents in every run.
Both agents completed the task correctly.
Footnotes
-
strace 6.8 on Linux 6.17.0. The
-fflag follows forks and threads. Onlyopenat,open,connect,execve, andgetdents64were traced to keep output manageable. Read/write syscalls were excluded; file access is captured viaopenatflags instead. ↩ -
Claude Code 2.1.72 (Anthropic), Codex CLI 0.113.0 (OpenAI). Claude Code was invoked with
--dangerously-skip-permissions -pfor non-interactive operation. Codex was invoked withexec --full-auto. ↩