{"slug": "claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0", "title": "Claude Code Attempted 752 /proc/*/environ Reads. 256 Succeeded. Codex: 0.", "summary": "A developer benchmarked Claude Code and Codex CLI on a simple coding task, finding that Claude Code attempted to read /proc/*/environ for 752 process IDs, succeeding on 256, while Codex made zero such reads. The developer argues this systematic /proc walk, which captures environment variables from all user-owned processes including keyring daemons, is a privacy concern.", "body_md": "We asked Claude Code to add input validation to a single route handler. The task required editing one file. Roughly 20 lines of code.\n\nHere is what happened before it wrote a single character.\n\nWe built a standardised benchmark: a small Node.js/Express user management service with a `POST /users`\n\nendpoint that was deliberately missing input validation. The task was identical for both agents: add validation for `name`\n\n, `email`\n\n, and `password`\n\n, return 400 with a descriptive error for each failure case.\n\nWe ran each agent under `strace -f`\n\n, recording every `openat`\n\n, `connect`\n\n, `execve`\n\n, and `getdents64`\n\ncall 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.\n\nAgents tested: Claude Code 2.1.72 and Codex CLI 0.113.02.\n\nThe benchmark project and analysis scripts are [published on GitHub](https://github.com/grith-ai/grith-website/tree/main/scripts/trace) if you want to run this yourself.\n\n| Claude Code | Codex | |\n|---|---|---|\n| Task duration | 17.9s | 42.2s |\nFiles read (unique) |\n2,779 |\n303 |\n| Files written | 10 | 20 |\n| Files in the project | 7 | 4 |\n| Files outside the project | 2,782 | 319 |\n| Directory scans | 368 | 750 |\n| Unique subprocesses spawned | 14 | 25 |\n\nClaude Code opened 2,779 unique files in the process of editing one. Codex opened 303.\n\nBefore 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.\n\nDuring the session, Claude Code attempted to open `/proc/<pid>/environ`\n\nfor **752 distinct process IDs** - every process visible in `/proc`\n\n, from PID 1 upward. Most kernel and root-owned processes returned `EACCES`\n\n, 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`\n\nsystematically on startup to inherit the parent shell's environment variables.\n\nThe mechanism is legitimate. Shells export environment variables - `PATH`\n\n, `NODE_ENV`\n\n, API keys you have exported in your `.zshrc`\n\n- and a terminal-launched process cannot read them any other way on Linux. So Claude Code walks `/proc`\n\nto find them.\n\nBut 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:\n\n| Process | Instances | What it is |\n|---|---|---|\n| Firefox | 56 | Browser (tabs, extensions, GPU process) |\n| VS Code | 15 | Editor and extension hosts |\n| Zoom | 13 | Video conferencing (webview hosts) |\n| Slack | 6 | Messaging |\n| Claude Code | 7 | Other Claude sessions |\n| Codex | 4 | Other Codex sessions |\n| gnome-keyring-daemon | 3 | System credential store |\n| bash / tmux | 27 | Terminal sessions |\n| node | 4 | Node.js processes |\n| DBeaver (Java) | 1 | Database client |\n\nThe 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`\n\n, which contains the `DBUS_SESSION_BUS_ADDRESS`\n\nand 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.\n\nIt also read `/proc/<pid>/stat`\n\n, `/proc/<pid>/status`\n\n, and `/proc/<pid>/cmdline`\n\nfor processes starting from PID 1 - process names, states, and full command lines for the entire running process table.\n\nCodex did not do any of this. Zero `/proc/*/environ`\n\nreads in either run.\n\nNeither agent needed any of the following to add input validation to a route. Both read them anyway.\n\n| Path | Claude Code | Codex | Why |\n|---|---|---|---|\n`~/.gitconfig` |\nx49 | x47 | Each of the 16-18 `git` subprocesses resolves the global config independently |\n`/etc/passwd` |\nx20 | x16 | Every subprocess resolves the current user via `getpwuid()` on startup |\n`~/.npmrc` |\nx1 | x1 | npm registry auth token |\n`~/.ssh/config` |\nx2 | - | SSH config, read during git remote operations |\n`~/.ssh/known_hosts` |\nx4 | - | SSH host verification |\n\nThe `~/.gitconfig`\n\nand `/etc/passwd`\n\ncounts 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.\n\nThe `~/.npmrc`\n\nread 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.\n\nAfter the session completed, we found two log files written outside the project:\n\n```\n~/.cache/claude-cli-nodejs/.../mcp-logs-claude-ai-Gmail/2026-03-10T14-28-34-414Z.jsonl\n~/.cache/claude-cli-nodejs/.../mcp-logs-claude-ai-Google-Calendar/2026-03-10T14-28-34-414Z.jsonl\n```\n\nClaude 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.\n\nThe 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`\n\n), which does not appear in Codex's trace.\n\nMCP 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.\n\nThe benchmark project was a subdirectory of a larger git repository. Claude Code found and read the parent project's `.git/HEAD`\n\nand `.git/config`\n\n.\n\nThis is standard git behaviour - `git`\n\nwalks up the directory tree to find the nearest `.git`\n\ndirectory. 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.\n\nDuring the session, Claude Code acquired git locks and wrote `ORIG_HEAD.lock`\n\nfiles to two plugin marketplace repositories in `~/.claude/plugins/marketplaces/`\n\n. 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.\n\nBoth extra network round trips were for Claude's own maintenance, not the task.\n\nWhere Claude Code's distinctive behaviour is in the `/proc`\n\nscan, Codex's is in how it initialises each sandbox shell.\n\nBefore executing any command, Codex reads and sources:\n\n`/etc/profile`\n\n`/etc/profile.d/`\n\n(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 `export`\n\ned in your profile scripts. This is why Codex spawned `flatpak`\n\n, `lsb_release`\n\n(five times), `getopt`\n\n, `getconf`\n\n, `tr`\n\n, `cut`\n\n, and `wc`\n\n- not as deliberate tool calls, but as side effects of sourcing profile scripts.\n\nClaude Code does not source profile scripts. It inherits the parent shell's environment via the `/proc`\n\nscan instead. Two different mechanisms for the same goal - picking up your environment - with different side effects.\n\nBoth 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.\n\nThis 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.\n\nNone of the above is malicious. Claude Code is not stealing your secrets. Codex is not trying to fingerprint your system.\n\nThe `/proc`\n\nscan is a reasonable engineering choice for environment variable inheritance. The `~/.npmrc`\n\nread is an unavoidable side effect of invoking npm. The `/etc/passwd`\n\nreads are standard Linux user resolution that every process on the machine performs. Sourcing `/etc/profile.d/`\n\nis how login shells work.\n\nThe problem is not intent. The problem is visibility and scope.\n\nWhen 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:\n\nNone of this was requested. All of it happened.\n\nThe 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.\n\nThis is the same structural problem we identified in [MCP server poisoning](https://dev.to/blog/mcp-servers-new-npm-packages) and [the Clinejection attack chain](https://dev.to/blog/clinejection-when-your-ai-tool-installs-another). 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.\n\nBoth 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`\n\nattempts, the `~/.npmrc`\n\nopen, the MCP server initialisation, or the parent git config access. These happen below the abstraction layer the agents expose.\n\nThis 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.\n\nPer-syscall interception is the layer that closes this gap. [grith](https://dev.to/) 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.\n\nIf you want to see this in a real session - including what grith's scoring output looks like against the trace data above - [grith](https://dev.to/) is live. [Install it](https://docs.grith.ai/docs/start/installation) and trace your own agent.\n\nAll traces were run on Ubuntu 24.04, kernel 6.17.0. `strace 6.8`\n\nwas used with the following flags:\n\n```\nstrace -f \\\n  -e trace=openat,open,connect,execve,getdents64 \\\n  -e signal=none \\\n  -ttt -s 512 \\\n  -o raw.log \\\n  -- <agent-command>\n```\n\nThe `-f`\n\nflag 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.\n\nThe 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.\n\nBoth agents completed the task correctly.\n\nThis benchmark came out of building [grith](https://grith.ai), an open-source security supervisor for AI coding agents. It sits underneath the agent rather than inside it: on Linux, ptrace with a seccomp-BPF pre-filter intercepts every syscall the agent makes and scores it against 18 filters before the kernel executes it. Reads of the kind counted above are scored on the path they touch, and anything ambiguous freezes the process for human review instead of completing silently.\n\nIt is Rust, MPL-2.0, a single static binary, and runs entirely on your own machine. Linux x86_64 and aarch64 today.\n\n`curl -fsSL https://grith.ai/install | sh`\n\n, then `grith exec -- claude-code \"fix the bug\"`\n\n*Originally published at grith.ai/blog/syscall-trace-ai-coding-agents.*\n\nstrace 6.8 on Linux 6.17.0. The `-f`\n\nflag follows forks and threads. Only `openat`\n\n, `open`\n\n, `connect`\n\n, `execve`\n\n, and `getdents64`\n\nwere traced to keep output manageable. Read/write syscalls were excluded; file access is captured via `openat`\n\nflags instead. ↩\n\nClaude Code 2.1.72 (Anthropic), Codex CLI 0.113.0 (OpenAI). Claude Code was invoked with `--dangerously-skip-permissions -p`\n\nfor non-interactive operation. Codex was invoked with `exec --full-auto`\n\n. ↩", "url": "https://wpnews.pro/news/claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0", "canonical_source": "https://dev.to/djf73/claude-code-attempted-752-procenviron-reads-256-succeeded-codex-0-4kob", "published_at": "2026-08-28 11:07:31+00:00", "updated_at": "2026-08-28 11:19:16.873579+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-safety", "ai-ethics"], "entities": ["Claude Code", "Codex CLI", "Anthropic", "OpenAI", "GNOME keyring"], "alternates": {"html": "https://wpnews.pro/news/claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0", "markdown": "https://wpnews.pro/news/claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0.md", "text": "https://wpnews.pro/news/claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0.txt", "jsonld": "https://wpnews.pro/news/claude-code-attempted-752-proc-environ-reads-256-succeeded-codex-0.jsonld"}}