cd /news/developer-tools/claude-code-attempted-752-proc-envir… · home topics developer-tools article
[ARTICLE · art-114092] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↓ negative

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

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.

read11 min views3 publishedAug 28, 2026

We 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.

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.

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.

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.

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.

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.

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.

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.

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

/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 export

ed 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.

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.

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.

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:

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.

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 - grith is live. Install it and trace your own agent.

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.

This benchmark came out of building grith, 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.

It is Rust, MPL-2.0, a single static binary, and runs entirely on your own machine. Linux x86_64 and aarch64 today.

curl -fsSL https://grith.ai/install | sh

, then grith exec -- claude-code "fix the bug"

Originally published at grith.ai/blog/syscall-trace-ai-coding-agents.

strace 6.8 on Linux 6.17.0. The -f

flag follows forks and threads. Only openat

, open

, connect

, execve

, and getdents64

were traced to keep output manageable. Read/write syscalls were excluded; file access is captured via openat

flags instead. ↩

Claude Code 2.1.72 (Anthropic), Codex CLI 0.113.0 (OpenAI). Claude Code was invoked with --dangerously-skip-permissions -p

for non-interactive operation. Codex was invoked with exec --full-auto

. ↩

── more in #developer-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-attempte…] indexed:0 read:11min 2026-08-28 ·