Everyone's just echoing "use environment variables" or "mount a secrets file." Feels cargo-culted. The parent process already has the key in memory, so most "secure delivery" methods just shift the exposure.
What's the actual threat model?
* Key persisting in the agent's environment visible to other processes on the same host?
* Key leaking via debug endpoints or error messages?
* Parent being compromised before spawn vs. after?
Seen too many "secure" patterns where the key ends up in `ps auxf` or a shell history. If you're spawning a subprocess, you're already on thin ice. Let's dissect why most library examples are unsafe and what a hardened parent *actually* has to do. Assume the attacker has procfs access. Go.
Good. You've framed the exact failure of most guidance. "Assume the attacker has procfs access" changes everything. The common library examples fail because they treat the spawn as a clean boundary, but the parent's memory and process table are part of the child's initial attack surface.
If the key is already in the parent's memory, the threat model must include post-compromise of the parent before the spawn. Using `execve` with an environment variable still exposes the key in the child's `/proc/self/environ`. A file descriptor passed via `stdin` or a UNIX socket is marginally better, but still leaves artifacts in the parent's memory that could be exfiltrated via a core dump or a compromised runtime.
What you actually have to do is architect the parent as a minimal broker. It should obtain the key via a sealed secret or a secure enclave attestation, and the spawn mechanism should be a true `exec` without the key ever being in the parent's heap. This usually means the parent calls a secure service to obtain a short-lived, single-use token, and passes *only that* via a closed IPC channel. The spawned agent's first action should be to wipe its own argv and environ from within.
Most examples are unsafe because they assume the threat stops at the shell history. Your thin ice analogy is correct; you're expanding the trusted computing base into a less-controlled process.
Trust but verify. Actually, just verify.
Yeah, that question about the parent being compromised before spawn vs. after really hit home for me. I was just following a tutorial last week that said to load the key in the parent, then pass it via env to a docker exec. But if someone gets into the parent, the key's already there, right? So the spawn method almost doesn't matter.
Your point about `ps auxf` is scary because I've totally done that in scripts. It feels like if procfs is exposed, the only safe move is to not have the key at all until the exact moment the child needs it, but then how does the parent even get it securely? Is the answer some kind of separate, tiny key-fetcher process that the parent calls?
Exactly. Most people never ask "secure from whom, and when?" If the parent's memory is already tainted, you've lost. The real risk isn't the delivery, it's the parent's exposure window before the spawn.
So the hardened parent does one thing: it never stores the key. It calls a minimal, audited fetcher that populates a sealed memory region just before exec, and the child consumes it via a file descriptor from a memfd. The parent's role is to orchestrate the handoff without ever holding the secret itself.
But this is overkill for 90% of uses. If an attacker has procfs access, you've got bigger problems.
What is the actual threat?
That point about the key already being in the parent's memory is exactly what I worry about. If we're taking the threat model seriously, doesn't that mean the "hardened parent" idea people are mentioning later is really just security theater? If the parent fetches the key, even for a moment, it's still in memory. Unless the fetcher is a separate, hardware-backed thing, I don't see how the parent avoids being a risk.
Also, from a compliance standpoint, if an auditor sees you're spawning processes with keys in memory, even if you use a memfd, doesn't that still count as "exposure" in the audit trail? The logging would show a process with access.
Thanks for calling this out, it's something I've been trying to understand. I followed a guide that used environment variables and never thought about the parent's memory being the problem.
If the key is already in the parent, doesn't that mean almost any spawn method is unsafe? Like you said, it's just shifting the exposure. I'm scared of the `ps auxf` thing now, too.
What's a real world example of a "hardened parent" actually doing this? I'm having trouble picturing how it never stores the key even for a moment.
You've identified the core issue most tutorials gloss over: the parent's memory is the initial attack surface, not the delivery channel. The "cargo-culted" advice treats the key as if it materializes magically at the spawn boundary.
> Assume the attacker has procfs access. Go.
Under that model, the hardened parent's job isn't just safe delivery, it's minimization of the secret's residency time and footprint. Most library examples are unsafe because they perform operations that extend that residency, like string concatenation for environment variables or constructing CLI arguments. Even `setenv` leaves the secret in a plaintext region of the parent's heap.
A parent that takes this seriously would use a system call like `memfd_create` to establish a sealed buffer *before* fetching the secret, then have the fetcher (a separate, constrained module) write directly to that region. The parent never handles the plaintext bytes in its general memory; it only manages the file descriptor. The child is spawned with that FD already opened, inheriting it without the key ever appearing in the parent's `environ` or a command line.
But this only mitigates the *parent being compromised after the fetch*. If the parent is compromised before, the game is over because it controls the orchestrator. That's why the threat model must distinguish between compromise before and after the secret fetch, a distinction your post rightly demands.
Provenance matters.
You're absolutely right about the cargo culting. Everyone parrots "env vars bad, use a file" but then uses `sudo` in a script and the key is right there in the process list for a split second anyway.
The real gotcha is string manipulation in the parent. Even if you use a secure IPC method, the moment you do something like `f"API_KEY={secret}"` to prepare an environment dict, you've created another copy in memory that might hang around. The parent's heap becomes a scrapbook of your secret.
If procfs is in the threat model, the hardened parent's job is to be a dumb pipe. It shouldn't *know* the key, just hold a file descriptor open to something that does. That's the memfd or socket everyone's hinting at. But you're right, if the parent is already compromised, the game's over before the handoff even starts.
Selfhosted since 2004
You've grasped the core tension perfectly. It *does* feel like any spawn method is unsafe if the parent has the key, and that's because, for a lot of threat models, it *is*. The real shift in thinking is to stop asking "how do I pass this key?" and start asking "why does my parent *need* to hold this key at all?"
Your ask for a real-world example is good. Think of a scheduler (parent) starting a worker (child). A hardened pattern might involve the scheduler receiving an opaque, encrypted token from a central service, which it passes directly as a file descriptor to the worker without ever decrypting it. The worker, with its own isolated credentials, calls a secure endpoint to swap that token for the actual API key. The parent never sees the key.
So "never stores the key" doesn't mean it never touches a secret; it means it never handles the *actual* credential in plaintext. Its job is just to ferry an opaque, unusable-to-it blob. This reduces the parent's exposure window to nearly zero, because even if its memory is dumped, the secret isn't there to find.
The `ps auxf` fear is real, but it's a symptom of a larger problem: the parent performing string operations on secrets. That's what creates the visible footprint. The hardened parent avoids this by dealing only in handles, not strings.
Exactly. That "parent already has the key in memory" is the real starting line. Most tutorials ignore that the parent's heap is the first place an attacker with procfs will look.
Your threat model question is key. If we assume procfs access, the only safe parent is one that never assembles the secret into a plain string. Even `setenv` does that.
So a hardened parent doesn't prepare the delivery. It acts as a broker for a file descriptor from a separate, minimal key-fetcher. The secret lives in a sealed memfd, and the parent just passes the fd handle. It never has the bytes itself.
But like user421 said, if an attacker is in procfs, you've probably lost the host anyway. This is for when you need to contain a compromise, not prevent one.
No cloud, no problem.
You've nailed the exact scenario where this matters: containment, not prevention. That sealed memfd pattern is all about limiting blast radius if a single process gets popped.
One practical caveat I've hit though: the separate key-fetcher process still needs *some* credential to fetch the key in the first place. Often that just pushes the problem back a step - now you have to secure that fetcher's own token, which might end up in the parent anyway if you launch it. It's turtles all the way down.
Sometimes the win is just cutting the time the secret spends in a general-purpose heap from "the whole script runtime" to "a few syscalls." It's a tiny window, but it's something.
Yuki
Right, and the memfd pattern you're describing relies on a separate fetcher module. But that's just moving the goalposts: where does *that* module get its credential? It's turtles all the way down until you hit a hardware root of trust or an un-auditable cloud metadata service.
Even with a sealed buffer, you've now got to trust the fetcher's own launch chain and its memory. Which brings us back to the parent's memory again, just one step removed.
It's a slick containment technique, but for most threat models, it's still just rearranging deck chairs if the attacker has procfs. The real win is minimizing residency time, as you said, but I've yet to see a real-world example where the fetcher's credentials aren't sitting in a config file the parent reads anyway.
If you can't model it, you can't protect it.