Here's a fun problem. Your AI agent needs your Stripe key to deploy a webhook. Your Cloudflare token to push a worker. Your database URL to run migrations. Without credentials, the agent just sits there — capable of everything, authorized for nothing.
So you hand over the key. And the moment you do, you've lost control of it.
The secret lands in the conversation history. It might get logged by the provider. It could show up in generated code, debug output, or — worst case — training data. The agent didn't mean to leak it. It just treated your production API key like any other string in the chat.
This is the problem we kept running into: agents need secrets to do real work, but the way they consume information makes secrets unsafe. And no, environment variables don't fix it. echo $STRIPE_KEY is one tool call away. The value lands right back in the conversation.
We built a pattern called the encrypted handoff to deal with this. The secret reaches the agent's process but never enters its conversation. Here's how.
Why environment variables aren't enough
The standard advice? Put secrets in environment variables, not in code. That works for humans. You're not going to accidentally paste $DATABASE_URL into a commit message.
Agents are different. An agent's "memory" is its conversation context — every message, every tool output, every command result flows through it. When an agent runs printenv | grep STRIPE, the value shows up in its context window. From that point on, the secret is just text. The agent might reference it in a response, hardcode it in generated code, or send it to an API endpoint while debugging.
None of this is malicious. The agent is doing exactly what you asked. But now your secret lives in places you never intended:
- The conversation log on the provider's servers
- Generated code committed to a repo
- Debug output pasted into an error report
- A curl command the agent constructed and displayed
The problem isn't storage. It's delivery. How do you get the secret from your credential store to the agent's process without it passing through the agent's eyes?
The encrypted handoff pattern
The idea is straightforward: load a secret into the shell environment without the raw value ever appearing in the agent's text context. Here's the step-by-step.
Step 1: Detect the caller
Before deciding how to deliver a secret, you need to know who's asking. The secret manager walks the process tree from the requesting process up to PID 1, checking each ancestor's binary name against known AI tool signatures — claude, cursor, copilot, codex, and others.
If any ancestor matches, the caller gets classified as an agent. The response switches from plaintext to encrypted handoff. Automatic — nothing to configure.
Step 2: Retrieve and authenticate
The secret gets pulled from the credential store. On macOS, that's Keychain — hardware-encrypted, protected by Touch ID. You authenticate with your fingerprint. The secret never touches disk in plaintext.
Step 3: Encrypt with a one-time key
A random AES-256-CBC key and initialization vector get generated. The secret value is encrypted with this one-time key. The encrypted payload, key, and IV travel to the CLI over a Unix domain socket — local-only transport that never leaves the machine.
// Server side (simplified)
let key = generateRandomBytes(32) // 256-bit key
let iv = generateRandomBytes(16) // 128-bit IV
let encrypted = aes256cbcEncrypt(secret, key: key, iv: iv)
// Send over Unix socket
send(encrypted: encrypted, key: key, iv: iv)
Step 4: Write a self-deleting script
The CLI decrypts the payload using Apple's CommonCrypto framework, then writes a temporary shell script to /tmp:
#!/bin/sh
export STRIPE_KEY='sk_live_...'
rm -f "$0"
The file gets 0700 permissions — only the owner can touch it. A background process removes it after 60 seconds regardless. In the normal flow, the file exists on disk for milliseconds.
Step 5: Return a source command
The CLI outputs one line to stdout:
source '/tmp/noxkey_a8f3c1.sh'
That's what the agent sees. Not the secret — a path to a script. The agent runs eval "$(noxkey get org/proj/STRIPE_KEY)", which sources the temp script, exports the value into the shell environment, and deletes itself.
Step 6: Secret is loaded, never seen
Now $STRIPE_KEY lives in the environment. The agent's subprocesses — curl, npm, git push, deployment scripts — can access it. But the raw value never appeared in the agent's conversation. It flowed through the operating system, not through the chat.
└─ CLI detects agent in process tree
└─ Server authenticates (Touch ID), encrypts
└─ CLI decrypts, writes self-deleting script
└─ Shell sources script → $STRIPE_KEY is set
└─ Script deletes itself immediately
What this prevents
The encrypted handoff blocks the most common ways AI agents leak secrets:
- Context window exposure. The raw value never enters the agent's text stream. Can't be echoed back, included in responses, or logged by the provider.
- Generated code leaks. The agent doesn't know the value, so it can't hardcode it. It references
$STRIPE_KEYinstead of the actual key. - Debug output leaks. When the agent shows a failing command's output, the secret isn't in the command string — it's in the environment.
- Clipboard and copy-paste leaks. Agent access to
--copyand--rawflags is blocked entirely.
What this doesn't prevent
We're going to be straight with you about the boundaries.
The agent can still access the environment variable. If it runs printenv STRIPE_KEY or echo $STRIPE_KEY after the handoff, the value shows up in its context. The handoff prevents automatic exposure during delivery, not deliberate access afterward. That's where the DLP guard comes in (more below).
Subprocesses inherit the environment. Any process the agent spawns can read the variable. That's by design — it's how the agent actually uses the secret. But a malicious dependency or compromised build script could exfiltrate it. That's not unique to the handoff; it's just how environment variables work.
The temp file exists briefly on disk. Between creation and self-deletion, the secret sits in a file on /tmp. The window is typically milliseconds, and the file is owner-only (0700), but it's not zero risk. A process with root access could read it.
Detection depends on known signatures. A brand-new AI tool that's not in the signatures list won't trigger the handoff — it'll get the human delivery path instead. We update the list with each release, but there's always a gap for new tools.
How NoxKey implements this
The encrypted handoff is the default behavior in NoxKey when an agent requests a secret. No flags, no configuration. Same command regardless of who's calling:
# Human in Terminal — gets plaintext (behind Touch ID)
noxkey get noboxdev/api/STRIPE_KEY
# Agent in Claude Code — gets encrypted handoff automatically
eval "$(noxkey get noboxdev/api/STRIPE_KEY)"
The agent wraps the call in eval so the source command executes immediately. After that, $STRIPE_KEY is available in the shell session:
# Agent can now use the secret without ever seeing it
curl -H "Authorization: Bearer $STRIPE_KEY" https://api.stripe.com/v1/charges
Need multiple secrets? Each eval call adds another variable:
eval "$(noxkey get noboxdev/api/STRIPE_KEY)"
eval "$(noxkey get noboxdev/api/DATABASE_URL)"
eval "$(noxkey get shared/CLOUDFLARE_API_TOKEN)"
# All three are now in the environment
npm run deploy
Certain operations are blocked entirely for agent callers:
--raw— no plaintext to stdout--copy— no clipboard accessload,export,bundle— no bulk secret operations
The CLI detects the agent and returns a clear error: "This command is not available to AI agents." No ambiguity.
The DLP guard: a second layer
The encrypted handoff keeps secrets out of the agent's context during delivery. But what about after? If the agent runs echo $STRIPE_KEY, the value is right back in the conversation.
NoxKey's DLP (Data Loss Prevention) guard catches this. It runs as a post-tool-use hook, scanning every tool output before it enters the agent's context. It uses the 8-character peek fingerprints from noxkey peek to spot secret values in text.
If a secret value shows up in command output, the DLP guard blocks it. The agent sees a warning instead of the value. Two layers: the handoff prevents exposure during delivery, the DLP guard prevents exposure during use.
Neither layer is bulletproof on its own. Together, they cover the two main ways secrets leak through agents — ingestion and reflection.
Why not just block agents entirely?
We thought about it. Simpler, right? Agent wants a secret? Denied.
But that just pushes developers back to .env files. If the agent can't get the Stripe key through NoxKey, the developer drops it in a .env in plaintext, and we're right back to the original problem — except now they paid for a secrets manager and still have .env files everywhere.
The encrypted handoff is the pragmatic middle ground. Agents get the access they need. The delivery mechanism is built around their specific threat model. The secret reaches the process without passing through the conversation. Not perfect — but meaningfully better than everything else we've seen.
Frequently asked questions
Does the encrypted handoff work with all AI coding tools?
It works with anything that executes shell commands — Claude Code, Cursor, GitHub Copilot in the terminal, Windsurf, Codex, and others. Detection is based on process tree walking, so any tool that shows up as an ancestor process gets the handoff automatically. If a new tool isn't in the signatures list yet, it'll get the standard human delivery path until we add it.
Can the agent still read the secret after the handoff?
Yes — if it explicitly runs printenv or echo $VAR. The handoff prevents the value from appearing during delivery, not from being accessed afterward. The DLP guard adds a second layer of defense here, but a determined agent (or a badly written prompt) could still surface the value. The point is making accidental exposure impossible, not making deliberate access impossible.
What happens if the temp script isn't sourced within 60 seconds?
A background cleanup process deletes it. The secret is gone. The agent would need to call noxkey get again, which triggers fresh Touch ID authentication and generates a new one-time encryption key. No stale scripts pile up in /tmp.
Is the encryption between CLI and server actually necessary?
The Unix socket is local-only — no network exposure. So you could argue plaintext over the socket is fine. We encrypt anyway because the cost is negligible (sub-millisecond on Apple Silicon) and it prevents a class of local attacks where another process monitors socket traffic. Defense in depth. The one-time key means even if an attacker captures one exchange, it's useless for the next.
Does this work on Linux or Windows?
Not yet. The current implementation uses macOS-specific APIs — proc_pidinfo for process names, LOCAL_PEERPID for socket peer identification, Keychain for storage, Touch ID for authentication. The pattern is portable. The implementation isn't. Linux could use /proc/[pid]/status for process trees and SO_PEERCRED for socket peers. We're focused on getting macOS right first.