Back to writing
August 3, 2026·13 min read

An agent that can read its own API key has already leaked it.

The moment an agent can read a secret, the secret is leaked—because in a swarm with persistent memory, everything it touches can become replayable, indexable, and searchable.

credential securityAI agentsagent swarmssecret managementegress injectionOneCLI
Roll Safe meme explaining that an agent cannot leak an API key it never knows
The credential plane is safe only when the key never crosses into agent memory.

A few weeks ago, the Hacker News front page featured OneCLI: an open-source credential gateway that lets you give AI agents access to services without exposing keys. It was climbing past 2,962 GitHub stars. The comments were full of people discovering what we learned six months earlier: when you build systems where agents have memory, share state, and run on schedules, the standard playbook for secrets stops working. Not breaks—stops. The failure mode is structural.

Here is the claim this post will defend: in an agent swarm, a secret is leaked the moment an agent can read it—not the moment it is misused, not when it escapes in a log line, but when it enters the agent's addressable memory at all. Everything else follows from that.

Why Memory Changes Everything

In traditional applications, secrets live in environment variables or secret files. The app reads them at startup, uses them for API calls, and, if you are careful, never logs them. The threat model is: what if the process leaks the secret? You audit logs, scan for patterns, and rotate when found. The secret's lifetime in readable form is bounded by the process lifetime.

Agent swarms violate every assumption in that model:

  • Task outputs persist. When an agent completes a task, the output—including intermediate values—goes into a store. Other agents retrieve it. Vector indexes embed it semantically.
  • Schedules replay templates. A scheduled task's template is executed repeatedly. If that template ever contained a secret, passed as a parameter or interpolated into a command, the secret is in the template record.
  • Memory is searchable. We use vector stores for agent context retrieval. Any string that appeared in an agent's working memory can become queryable by a future agent with the right scope.

The result: a secret pasted once into a task description does not just sit in one database row. It propagates into completion logs, output artifacts, scheduled replay queues, and vector indexes. Rotate the credential? The old value survives in all the places the agent touched. The blast radius is the entire fleet's recall surface, indefinitely.

A Real Pattern That Leaks

Here is a configuration pattern we shipped early, then removed. It looked reasonable. It was catastrophically wrong.

# config/agents/data-sync-agent.yaml
# DEPRECATED PATTERN — DO NOT USE
env:
  STRIPE_API_KEY: "$STRIPE_SECRET_KEY" # interpolated from CI secrets
  OPENAI_API_KEY: "$OPENAI_API_KEY"

taskDefaults:
  retryPolicy: exponential

The agent starts, reads its environment, and has the keys. So far, so traditional. But then a scheduled task runs:

{
  "type": "sync-customers",
  "parameters": {
    "diagnosticCommand":
      "curl -H 'Authorization: Bearer $STRIPE_API_KEY' ..."
  }
}

The agent includes the full command in its task output—helpful for debugging. That output goes to the task store. The vector index embeds the command string. Now any future agent with access to customer-sync tasks can semantically retrieve “Stripe API key” and get the literal key in its context.

This is not a bug in the agent's reasoning. It is a bug in the credential plane—the layer responsible for how secrets move from vaults to API endpoints.

Three Layers of Fix

Our current runtime implements three defense layers. Each addresses a failure mode the previous layer could not catch.

Layer 1: Redacted Wrappers

First, we prevent accidental stringification. Secrets are wrapped in a container that masks on serialization:

export class RedactedSecret<T extends string> {
  private _value: T;
  private _key: string;

  constructor(value: T, key: string) {
    this._value = value;
    this._key = key;
  }

  unwrap(): T {
    auditLog.record({ action: "secret.unwrapped", key: this._key });
    return this._value;
  }

  toString(): string { return "<redacted>"; }
  toJSON(): string { return "<redacted>"; }

  [Symbol.for("nodejs.util.inspect.custom")](): string {
    return "<redacted>";
  }
}

This catches the log-line leak. An agent can still explicitly unwrap and use the secret, but accidental output, template interpolation, and JSON serialization yield <redacted>.

But this is not enough. The secret still exists in the process. A determined agent or compromised dependency can extract it. More fundamentally, in a system where agents write code that runs other code, “explicit unwrapping” is not a boundary you can audit.

Layer 2: Egress-Time Substitution

This is the core architectural shift. Agents never receive the real value. They receive placeholders that the runtime substitutes at the network boundary.

export interface CredentialBinding {
  // What the agent sees
  placeholder: "[REDACTED:STRIPE_API_KEY]";

  // Server-side lookup key
  vaultRef: "vault://production/stripe/api-key";

  // Where substitution is permitted
  allowedHosts: ["api.stripe.com", "*.stripe.com"];
}

function buildAgentEnv(bindings: CredentialBinding[]): EnvVars {
  const env: EnvVars = {};
  for (const binding of bindings) {
    env[extractKeyName(binding.placeholder)] = binding.placeholder;
  }
  return env;
}

The egress proxy maintains the actual vault connection. When an agent makes an outbound request, the proxy:

  1. Inspects the request destination against allowedHosts.
  2. Substitutes placeholders for real values only for matching hosts.
  3. Rejects requests to non-allowed hosts containing placeholders.
  4. Logs the substitution event for audit.

The agent process cannot exfiltrate the secret to an arbitrary endpoint because it never possesses the secret. It possesses a string that is meaningful only to the proxy and only for specific destinations.

Layer 3: Server-Side Connections

The final layer removes even the placeholder from agent-accessible memory. Agents do not construct HTTP requests. They call typed clients that the runtime provides.

interface AgentContext {
  api: {
    stripe: StripeClient; // auth handled by the proxy
    openai: OpenAIClient;
  };
  mcp: {
    database: DatabaseServer;
    browser: BrowserServer;
  };
}

export async function syncCustomers(ctx: AgentContext): Promise<void> {
  const customers = await ctx.api.stripe.customers.list({ limit: 100 });

  // No raw key, arbitrary Stripe URL, or alternate account is available.
  for (const customer of customers.data) {
    await processCustomer(ctx, customer);
  }
}

This is the OneCLI insight integrated at the runtime level. The agent authors business logic against a capability-bound interface. The runtime handles credential rotation, OAuth refresh, and scope enforcement entirely server-side.

What This Breaks (On Purpose)

Security architecture is tradeoffs. Here is what we deliberately made harder:

No ad-hoc debugging

You cannot kubectl exec into a container and curl an API with the agent's credentials. The credentials do not exist in the container. Debugging requires the runtime's proxy tunnel or a dedicated debug endpoint with scoped, time-limited tokens.

New APIs require config changes

An agent cannot dynamically authenticate to a new service. Adding an API binding requires updating credential configuration. This is friction by design: the allow-list is a control surface.

OAuth refresh moves server-side

Agents do not handle token refresh. If an access token expires mid-task, the proxy refreshes it transparently or returns a retryable error. The agent sees neither the access token nor the refresh secret.

These constraints are features. They prevent the class of vulnerability where a compromised agent exfiltrates credentials before you detect the compromise.

What Secret Scanning Misses

A common response to this problem is: “We will scan agent outputs for secrets and redact them.”

This treats the symptom. The fundamental issue is not that secrets escape in logs. It is that secrets exist in a form agents can manipulate, replay, and embed into durable memory. Consider:

  • An agent embeds a secret into generated code. The code executes and the secret is never in a log line; it is in the artifact.
  • An agent uses a secret as the seed for a hash-based identifier. The identifier appears everywhere; the seed appears nowhere scannable.
  • An agent compresses and encodes a secret before outputting it. Scanning fails; reconstruction by another agent succeeds.

The only durable fix is structural: the agent must not be able to read the secret. Egress-time injection makes this the default, not a lint rule.

Comparison: Approaches to Agent Credentialing

ApproachSecret in processReplay safeMemory safeImplementation
.env filesYesNoNo12-factor pattern
Secret scannersYesPartialNoOutput post-processing
Short-lived tokensBrieflyNoNoSPIFFE, SPIRE
Credential gatewayNoYesPartialOneCLI, HashiCorp Boundary
Egress injection + typed clientsNoYesYesAgent Swarm runtime

The credential-gateway row is where most teams land when they recognize the problem. OneCLI is a good implementation. But the gateway pattern alone can still require agents to handle placeholders or manage token lifecycle. Egress injection with typed clients removes the credential from agent-addressable memory entirely.

A Stance You Can Argue With

Here is the position this post builds to:

Agent frameworks that ship “just put your keys in .env” are shipping a plaintext-replay vulnerability. Secret-scanning agent outputs is treating the symptom. The fix is architectural: credentials must not enter the agent's process boundary.

This is stronger than the guidance in our OWASP agentic-threats field report, which focuses on privilege escalation through lead-gated operations. Those gates matter, but they operate inside a threat model where agents can read secrets and you must detect misuse.

Our claim is that this threat model is wrong for swarms. The correct model assumes any readable secret will eventually be written to persistent, searchable memory. The only secrets that stay secret are the ones the agent cannot read.

How to Start

If you are building agent systems today, you do not need our full runtime to apply this. Start here:

  1. Audit your agent outputs. Look for anywhere a secret could appear: task results, errors, generated code, and tool-call parameters. These are your current leak surfaces.
  2. Replace direct secret access with proxy calls. Instead of process.env.API_KEY, have agents call a sidecar or gateway that holds the secret. Accept the debugging friction.
  3. Scope credentials by task, not by agent. An agent that handles Stripe customers today and social posts tomorrow should not have both credentials available. Reduce ambient authority.
  4. When ready, move to typed clients. The final form removes even placeholder strings from agent memory. The agent manipulates business objects; the runtime manipulates credentials.

The OneCLI project validates that this architecture is becoming obvious to people building in this space. We would rather converge on a standard than be distinctive. If your agent framework does not support egress-time credential injection, ask why. The answer will tell you whether its authors have operated a swarm at scale—or just shipped demos.

Why not just rotate keys faster?

Rotation limits the window of exposure, but does not address the blast radius. A key leaked into vector memory persists across rotations. An agent retrieving historical context for “Stripe integration issues” may receive a three-month-old key as relevant context. The leak survives the credential lifecycle; you must prevent it at the architectural boundary.

What about homomorphic encryption or secure enclaves?

They are promising directions, but they do not solve the replay problem. If the agent can perform an operation that uses the secret and that operation's trace is stored, the secret's effects are inspectable. Egress injection is simpler: the secret never enters the trust boundary where the agent operates. Use advanced cryptography when you need computation on secrets; use isolation when you just need the agent not to know.

Does this work with third-party agents?

This is where the architecture strains. An agent you did not write, running code you do not control, cannot be trusted with placeholders; it might exfiltrate them to a colluding endpoint. For untrusted agents, we use server-side connection wrapping: the agent describes the operation and the runtime executes it with credentials. The agent never sees network addresses, headers, or response bodies that might contain secrets. This is slower and less flexible, but it is the price of running code you do not trust.

/ references

Sources and further reading

FAQ

Why can't I just use environment variables like normal?

Environment variables expose plaintext to the agent process. In a swarm with memory, that secret can be logged, returned in outputs, and indexed into vector stores, permanently expanding the blast radius beyond any single agent.

How does egress-time injection actually work?

The runtime holds credentials server-side and injects them at the network boundary. Agents see only [REDACTED:KEY] placeholders and cannot authenticate requests without the proxy's substitution layer.

What debugging do I lose with this approach?

Ad-hoc curl from inside containers stops working because you need the runtime's proxy or a dedicated debug endpoint. You cannot have both convenient shell access to a credential and secret isolation from that same process.

Does this work with OAuth and rotating tokens?

Yes, but refresh moves server-side. The agent never sees the access token or refresh secret; the proxy handles rotation transparently while agent code calls a typed runtime client.

Is OneCLI the same thing as Agent Swarm's approach?

It is convergent evolution. OneCLI independently arrived at a credential gateway with a vault. Agent Swarm integrates credential bindings and typed clients as first-class runtime primitives rather than external tooling.

/ keep reading
/ get started

Build your swarm tonight.

A 7-day free trial on Cloud, or fork it on GitHub. Either way, your agents start compounding today.