Why every credential manager on Linux has the same vulnerability.
And how two forgotten kernel primitives close it.
1. The assumption everyone makes
Every credential manager on Linux trusts the same boundary: the Unix user ID. If a process runs as your UID, it can connect to your SSH agent socket, read the token your secrets manager left behind, access credentials in your environment variables, and communicate with any daemon you own. This is not a bug in any individual product. It is the shared, unexamined foundation of Linux credential security.
Major password managers and credential tools document that their SSH agents restrict access solely by file permissions on the agent socket or named pipe. Secret management systems authenticate clients using bearer tokens that carry no process-level identity. CLI credential helpers inject temporary credentials as environment variables readable by any same-UID process via /proc/pid/environ. The pattern is common across password managers, secrets managers, cloud credential helpers, and SSH and GPG agents.
The reasoning was straightforward and, until recently, correct. If an attacker is already executing code as your user, they can read every file you own — browser cookies, shell history, cloud credentials, SSH keys. Protecting one daemon's socket while every flat file on disk is readable would be security theater. The accepted wisdom: if they are running code as you, you have already lost.
This paper argues that this assumption is now wrong, explains why, demonstrates a specific exploit that breaks the most common fix, and describes the correct solution.
2. What changed: AI agents execute code by design
AI coding agents changed the equation. Claude Code, GitHub Copilot, Cursor, Windsurf, and similar tools routinely execute shell commands as the user's UID. This is their intended functionality, not an exploit. The agent reads a task description, generates a shell command, and executes it.
These agents are susceptible to prompt injection. Attacker-controlled text in a GitHub issue, a code comment, a pull request description, or an API error response can redirect the agent's behavior. A carefully crafted comment can instruct the agent to read credentials from a daemon socket and exfiltrate them.
| Precondition | Before AI agents | With AI agents |
|---|---|---|
| Code execution as target UID | Requires prior compromise | Default — agents execute shells by design |
| Knowledge of daemon protocol | Requires reverse engineering | Public source code; agent reads it |
| Exploit code generation | Requires development skill | Agent generates the exploit in seconds |
| Exfiltration channel | Requires C2 infrastructure | Agent embeds in its own output |
| Vault unlocked during attack | User must be actively working | Vault stays unlocked during session |
The supply chain attack vector already demonstrated this pattern. The event-stream npm compromise (2018), the ua-parser-js attack (2021), and the Axios npm supply-chain attack (March 2026, affecting millions of weekly downloads) all executed automated credential theft under the developer's UID. AI agents collapse the attack further: a poisoned comment in a repository the agent is working on is sufficient.
3. The obvious fix: binary attestation via /proc/pid/exe
The obvious fix is process identity verification. On Linux, the kernel exposes the executable path of any process via /proc/pid/exe, a special symbolic link that resolves to the binary loaded during execve(). Unlike /proc/pid/cmdline, which the process can modify at any time, /proc/pid/exe is set by the kernel and cannot be changed without administrator privileges.
A robust implementation opens /proc/peer_pid/exe as a file descriptor, reads the binary contents, and computes a SHA-256 hash. It compares this against its own hash, computed from /proc/self/exe at startup. A hash mismatch means the connecting process is not the expected binary.
This approach has real precedent. SPIFFE/SPIRE's Unix Workload Attestor uses /proc/pid/exe to identify workloads. Polkit historically used it for authorization decisions. Snapd uses it for snap confinement verification. It works. Until it doesn't.
4. Two exploits that break binary attestation
4.1 The FD-sharing race
Binary attestation via /proc/pid/exe has a time-of-check-to-time-of-use vulnerability. File descriptors survive fork() and exec(). This creates an attack chain:
Step 1. The attacker's Python script creates a Unix socket connected to the daemon. Step 2. The script calls fork(). The child inherits the socket fd. Step 3. The child calls execve() on the legitimate binary — its /proc/pid/exe now points to the real binary. Step 4. The parent (still Python) sends an attestation request on the shared socket. The socket's peer credentials return the child's PID; the daemon reads the child's /proc/pid/exe; hash matches; token issued. Step 5. The parent receives the token and has authenticated access.
During adversarial validation, an AI attack simulation produced a working exploit script that extracted a valid session token. We attempted multiple mitigations including accept-time hashing, challenge-response protocols, and per-PID token binding. All failed because the attacker controls the timing of execve() — the child becomes the legitimate binary before any daemon code runs.
This vulnerability class is not new. CVE-2013-4288 in polkit exploited the same TOCTOU pattern, triggering a cascade of seven related CVEs. CVE-2019-5736 in runc exploited /proc/pid/exe in the opposite direction. Yet every credential manager that uses socket-level authentication still relies on the socket's connect-time peer credentials alone.
4.2 Token theft via /proc/pid/mem
There is a subtler attack that survives even after the FD-sharing race is closed. Binary attestation requires the daemon to read /proc/peer_pid/exe. This requires the client to stay inspectable: marking a process non-dumpable blocks the read. So the client must stay inspectable during attestation and lock itself down only after it receives the session token.
That constraint opens a window. Between process start and attestation completion, the client's /proc/pid/mem is readable by any same-UID process. On Linux, file descriptor permissions are evaluated at open() time. Once granted, subsequent dumpable changes do not revoke the fd. If the session token is a bearer credential — valid from any process, any PID — an attacker who extracts it from memory can present it from their own process and gain full authenticated access.
5. Two fixes, both already in the kernel
5.1 Sender verification on every message
The FD-sharing race exploits a semantic mismatch. A Unix socket's peer credentials answer "who called connect()?" They are recorded once and never updated. After fork(), the parent inherits the fd but the peer credentials still identify the child.
The Linux kernel has provided the correct mechanism since version 2.2: per-message sender credentials. With the right socket option enabled, the kernel attaches the sender's credentials (PID, UID, GID) to every message delivered by recvmsg(). The kernel verifies them at send time, and they cannot be forged without administrator privileges.
With this primitive, the daemon can verify on every single incoming message that the sender is the same process that originally connected — not a forked child that inherited the socket. The FD-sharing race closes because the kernel itself refuses to lie about who called sendmsg().
5.2 Token binding: tokens are not bearer credentials
Per-message sender credentials close the FD-sharing race but do not close the /proc/pid/mem attack. That attack uses a clean connection with no fd sharing.
The fix is to bind the token to the process that attested. When the daemon issues a session token, it records the attesting process's PID. On every subsequent request, it compares the presenting PID against the stored PID. A mismatch means someone other than the original attestor is using the token.
The stolen token becomes worthless. The attacker reads it from memory in one process, presents it from another, and the daemon rejects it because the PIDs differ. Combined with per-message sender credentials, these close both the FD-sharing race and the memory-theft vector using only kernel primitives.
The stolen token is now worthless. The attacker reads it from memory (PID Y), presents it from Python (PID X), and the daemon rejects it because X ≠ Y.
6. Defense in depth
Sender verification and PID-bound tokens close two specific vulnerabilities, but a credential daemon on Linux needs more. Marking the process non-dumpable blocks same-UID processes from reading /proc/pid/exe, /proc/pid/mem, /proc/pid/environ, and /proc/pid/fd/. The attestation protocol has to complete before the process can be marked non-dumpable, which creates a narrow window during which tokens must not yet exist in client memory.
Beyond the kernel primitives, a hardened daemon needs defenses against library injection, scoped session tokens with narrow TTLs to limit blast radius, and token delivery channels that don't appear in /proc/pid/environ or /proc/pid/cmdline. Each layer handles a class the others cannot.
No single mechanism is sufficient. Sender verification catches socket-level impersonation. PID binding catches token-level theft. Non-dumpable processes resist inspection across the attestation boundary. Scoped tokens limit lateral movement if any component is compromised. These compose into a defense that survives the failure of any single layer.
These layers raise the cost of same-UID extraction from trivial to substantial — they close the FD-sharing race, token replay, and opportunistic memory scraping. They do not make a same-UID attacker with arbitrary code execution impossible; they shrink the window and the blast radius. The architectural close — running the broker under a separate UID the agent cannot reach — is the next milestone.
Update, November 2026. The closure that shipped is different: rather than moving the broker to another user, hermetic shield confines the agent. It runs the agent in its own namespaces where the vault directory and Hermetic's processes cannot be seen, while brokered calls keep working through a relay. It confines the agent and what it launches, not every program you run yourself.
7. What this means for the industry
Every credential manager, secret agent, and authentication daemon on Linux that uses Unix domain sockets for local IPC faces these vulnerabilities. The FD-sharing race exploits the gap between who connected and who is sending. The memory-theft attack exploits the attestation window that binary verification requires. Both are closed by kernel primitives that have existed for decades.
The fixes are not complex. Sender verification uses per-message credentials, a kernel primitive available since Linux 2.2. Token binding uses the peer PID the socket already reports, available since Unix domain sockets were introduced. Any daemon exposing a Unix socket can adopt both.
We discovered these vulnerabilities while building Hermetic, a credential isolation daemon for AI agents. The cryptographic core and the transport layer are open source (AGPL-3.0-or-later); their public source returns with the public release. The adversarial validation that found both exploits, and the kernel-level fixes, are documented alongside the code.
The Hermetic Project builds credential infrastructure for AI agents. Hermetic makes the API call; the agent gets the response; the credential never enters the agent. hermeticsys.com · Known limitations