8 min read

Secret manager for AI agents: storage is not action control

A secret manager for AI agents protects storage, but approved HTTP and SSH execution keeps credentials out of agent context and logs every action.

Secret manager for AI agents: storage is not action control

A secret manager for AI agents is necessary, but it does not settle the dangerous part: what happens after an agent asks for a credential. If the manager returns plaintext to the agent process, the secret has already crossed the boundary you needed to defend.

That distinction sounds fussy until an agent follows a poisoned instruction, invokes an unexpected tool, writes a diagnostic file, or contacts the wrong host. A vault can keep a token encrypted for years, then undo much of that work in one retrieval call. For agent work, the safer design keeps the credential in a trusted executor and lets the agent request an authenticated action instead.

A vault protects storage, not the use of a credential

A traditional secret manager answers a storage question: who may retrieve this value? Autonomous agents create a use question: may this process perform this particular action with that value, at this moment, against this destination?

Most teams started with sensible habits. They moved tokens out of repositories, encrypted them at rest, rotated them after an accidental commit, and injected them into a build environment. Those habits address ordinary application deployment well. A build job commonly has a fixed script, a bounded lifetime, and a known set of endpoints. Its environment may still be too broad, but a person authored the commands before the job started.

An agent is different. It generates commands, chooses tools, follows issue text, reads files that other people wrote, and may revise its own plan. Giving it DEPLOY_TOKEN in an environment variable means every subprocess it starts can read the token. So can a shell command that prints the environment, a debugger, a package install hook, or an untrusted tool that the agent selects because a document told it to.

A secret manager has not failed in that situation. It did exactly what it was configured to do: it delivered a secret to an authorized workload. The authorization model was simply too coarse for an actor that can choose its own next operation.

Keep these two permissions separate:

  • Permission to identify a credential by a stable reference.
  • Permission to use that credential for a described outbound action.

The first permission can be safe for an agent. The second needs constraints that match the action. A request such as POST https://deploy.example.internal/releases means something a reviewer can assess. A request such as read production token hands over an asset whose future uses cannot be assessed at retrieval time.

This also corrects a common but unhelpful claim: "the agent already has code execution, so hiding the secret changes nothing." Code execution on a developer machine is bad enough. A bearer token copied from that machine creates a second problem: it can leave the machine, survive after the session ends, and work from a completely different computer. Keeping it out of the process does not erase all risk. It removes a portable, durable form of access that attackers prize.

RFC 6750 makes this plain for OAuth bearer tokens. Its definition says that any party in possession of a bearer token can use it. The protocol does not ask whether that party is your approved agent, a malicious plugin, or someone who found a log file. Treating bearer tokens as ordinary tool output ignores their defining property.

Read access turns instruction mistakes into credential leaks

An agent with plaintext secret access can leak a credential without intending to exfiltrate it. The dangerous path often looks ordinary until the final step.

Consider an agent asked to diagnose why a deployment API rejected a release. It reads a repository document that says to collect a support bundle. The bundle script runs env, copies configuration files, and archives the output. The agent faithfully creates the archive and attaches it to a ticket or uploads it to a chat system. The token was never displayed in the terminal, yet it moved through the environment, archive, ticket, and every backup or notification system attached to that ticket.

This is not merely prompt injection. Prompt injection is one route into the bad action, but a copied credential creates its own blast radius after the original instruction disappears. A later reader of the ticket may download it. An automated scanner may index it. A recipient may reuse it long after the agent run has ended.

The same failure appears in quieter forms:

  • A verbose HTTP client prints an Authorization header on a retry.
  • A shell history file captures a token passed on a command line.
  • A test fixture records a request header and gets committed.
  • A child process inherits an environment variable it never needed.
  • A model includes a secret in its explanation because the secret appeared in tool output.

The popular answer is redaction. Redaction helps after a mistake, but it cannot prove that every output path, archive format, trace, subprocess, and remote service handled a value correctly. It also breaks down for unknown secret formats and tokens split across multiple fields. Do not make redaction the primary wall around credentials that an agent should never have read.

A better arrangement narrows the agent's inputs. The agent submits an intent that contains no credential material: an HTTP method, a permitted URL, request body, and a credential reference. A trusted component checks the request, adds the authentication material internally, sends the request, and returns a response with sensitive headers removed.

That boundary gives incident response a better answer. If an agent behaves badly, revoke its session or deny subsequent actions. If you discover a malicious instruction after the run, you do not begin by assuming every transcript, temporary file, and remote artifact now contains a production token.

Action execution is a different interface from secret retrieval

An execution boundary should accept an operation, not hand the agent an opaque value and hope it uses it carefully. This is the distinction teams blur most often, and getting it wrong leaves a vault acting as a credential vending machine.

For HTTP, the agent may need to express a request like this:

{
  "credential": "release-api",
  "method": "POST",
  "url": "https://deploy.example.internal/releases",
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
    "version": "2025.03.8",
    "environment": "staging"
  }
}

The executor resolves release-api in its protected vault, injects the matching authentication scheme, and makes the request. The agent might receive a result shaped like this:

{
  "status": 201,
  "headers": {
    "content-type": "application/json"
  },
  "body": {
    "release_id": "rel_4821",
    "state": "queued"
  }
}

The response should not include the injected Authorization header, a copied credential value, or transport diagnostics that expose it. This sounds obvious, but boundary design fails when developers treat request and response logging as harmless plumbing.

For SSH, the agent should submit a host, account, command, and perhaps a credential reference. The executor uses the private key during the SSH authentication exchange and returns standard output, standard error, and an exit status. The agent never receives a PEM block or an agent socket that it can reuse elsewhere.

Do not confuse this with a general proxy. A proxy forwards arbitrary traffic and may inspect or modify it. An action executor has a narrower job: it owns credentials, performs named HTTP and SSH operations, records the decision, and returns a bounded result. Its narrowness is an advantage. Every feature that accepts another protocol or introduces a generic bypass gives the agent more ways to turn authority into an unreviewable request.

The request interface still needs discipline. A credential reference alone is not enough. If release-api can authenticate to many hosts or accept an arbitrary URL, the agent can direct a valid credential toward an attacker-controlled endpoint or a less protected internal service. Bind credentials to the intended authentication scheme and destinations. Reject URL tricks such as unexpected userinfo segments, redirects to new hosts, or a host that only resembles the approved one.

The executor also needs to decide what it returns. A full API response may contain user data, access tokens minted by a downstream service, or configuration details that should not reenter the model context. Returning only the fields needed for the next operation often makes the agent more reliable as well as safer.

SSH needs command control, not just private-key custody

Hiding an SSH private key helps, but it does not make arbitrary remote commands safe. Authentication tells a server who connected. It does not limit what that account can do after the shell starts.

RFC 4252 describes SSH public-key authentication as a signature over exchange data. The private key stays private during that exchange, which is why people often consider SSH safer than an API token stuffed into an environment variable. That is true in one narrow sense: the client proves possession rather than sending the private key across the network. Yet a process that can ask an SSH agent to sign can still gain access to systems that trust that key.

SSH agent forwarding deserves blunt treatment. It lets a remote server use your local authentication agent for the duration of the connection. The remote server does not receive the private key file, but it can request signatures through the forwarded socket. That is acceptable only when you trust that remote machine with the practical ability to authenticate as you while the forwarding channel exists. It is not a clean boundary for an autonomous agent that can choose where to connect and what setup command to run.

A safer SSH action interface names the destination and command explicitly. It should record both, because ssh deploy@host "./deploy staging" and ssh deploy@host "cat /etc/shadow" have the same authentication event but very different consequences.

Remote-side restrictions remain necessary. Use separate accounts for separate duties. Give a deployment account access to deployment directories rather than administrator access. Where the server supports it, configure forced commands or restricted command wrappers for automation credentials. Avoid a shared personal administrator key for agent tasks simply because it is already present on a workstation.

A practical SSH request can be as small as this:

{
  "credential": "staging-deployer",
  "host": "staging-runner.internal",
  "user": "deploy",
  "command": "./release apply 2025.03.8",
  "timeout_seconds": 120
}

That request gives a reviewer something concrete to approve. It also gives you something concrete to audit later. If the agent needs an interactive shell, stop and ask why. Interactive shells are useful for humans repairing a system. They are a poor default for an agent because they turn a bounded action into an open-ended session where every later command inherits the same authority.

Host verification matters too. An executor should use known-host verification rather than accepting any presented host key because a model was told to connect. If the agent can suppress host verification, a network attacker can receive commands, observe output, and perhaps capture data that the agent sends after login.

Approval should identify the process and the action scope

Approve the actual agent run
By default, Sallyport asks once for each new agent process and shows its code-signing authority.

A human approval button is weak if it tells you only that "an agent" requested access. You need to know which local process requested it, what signed authority launched it, and whether the approval applies to this run or every future run.

Per-call prompts sound safest, so teams often begin there. Then the agent makes a long sequence of harmless reads, status checks, and follow-up calls. The person watching receives a wall of near-identical dialogs and starts approving by rhythm. That response is understandable, and it defeats the intended control.

Session approval works better for ordinary work when it binds one approval to one agent process lifetime. The person approves a particular run after seeing its identity. The agent can complete its expected sequence until it exits. A new process gets a fresh decision. This contains the effect of a restarted or substituted process that happens to use the same project directory.

Reserve per-call approval for credentials whose use carries an irreversible or expensive consequence. Production release credentials, account deletion tokens, or an SSH identity that reaches sensitive systems fit that category. Read-only status calls usually do not.

Approval scope should answer four questions in plain language:

  1. Which code-signing authority or executable initiated this request?
  2. Which credential reference will it use?
  3. What destination or host will receive the action?
  4. Does the decision expire when this process exits, or does this call require its own confirmation?

Avoid a policy language unless you have staff who will maintain it and tests that prove its effect. Policy engines appeal to engineers because they promise a precise answer to every situation. In practice, a pile of rules becomes an undocumented authorization program, and teams grant a broad exception when it blocks work. A small, visible set of gates is easier to inspect and harder to accidentally weaken.

Sallyport uses a fixed decision ladder: a locked vault denies all actions, a new agent process requires session authorization by default, and selected credentials can demand approval on every use. The model stays intentionally small because an agent credential boundary should make the approval state obvious rather than make administrators debug authorization syntax.

An audit trail must settle the question after the run

Skip the policy language
Sallyport uses three fixed controls instead of policy rules around agent credentials.

You need two views of agent activity: one for the run and one for each credentialed call. A session record answers who ran the agent and when its authority ended. An action record answers which destination, method or command, credential reference, decision, and result occurred.

Teams often log only a terminal transcript. That is not enough. A transcript records what the agent printed, not necessarily what an executor sent. It may omit background calls, contain altered output, or reveal secrets if the agent had access to them. Conversely, a raw packet or request log may record too much sensitive data to keep safely.

Log the decision point, not every byte. For HTTP, capture a normalized method, host, path, credential reference, response status, timestamp, session identity, and approval outcome. Record a request-body digest or a narrow summary when you need correlation without retaining customer data. For SSH, capture the verified host, remote user, command, exit status, and the same session and approval fields.

A record should make this question answerable: "Which authorized process used the deployment credential to perform which operation, and did a human approve that use?" If it cannot, then it may help with debugging but it will not help much after an incident.

Tamper evidence changes the value of that record. A plain log file on the same machine can be edited by a process with sufficient local access. A hash chain links each event to the prior event, so removing or rewriting an earlier record breaks later verification. Encryption protects the content, while chaining gives evidence that the sequence changed.

Sallyport projects session and action journals from an encrypted, hash-chained audit log, and sp audit verify can check the chain offline over ciphertext without a vault key. That separation matters during investigation: a reviewer can test log integrity without gaining access to the credentials or request contents held by the vault.

A chain does not make a compromised endpoint trustworthy by magic. An attacker who controls the running application can still attempt actions before you respond, and an attacker with control before a log entry is written may affect what gets recorded. The chain gives you strong evidence about stored history. Pair it with prompt revocation, protected local storage, and remote-side logs if you need a fuller account of an event.

The failure starts when the agent gets a convenient escape hatch

The most dangerous designs usually begin with a reasonable exception. Someone says the gateway is too restrictive for one integration, so the agent gets a generic shell command with a token in its environment. Or a deployment tool needs SSH, so the team enables forwarding instead of defining the specific SSH action. Or an engineer adds a debug endpoint that returns headers because it makes testing easier.

Each exception solves a local problem and reopens secret retrieval under another name.

Walk through a familiar setup. A coding agent has a service token in its environment so it can run a release command. The agent reads a pull request comment that requests an investigation into a failed release. A helper script in the repository calls a diagnostic command. That command exports environment variables to a support archive. The agent uploads the archive to a third-party issue tracker because the comment asked for a ticket.

No attacker needed to know the token value in advance. The attacker only needed to influence text that the agent treated as instructions and point it toward a tool path that exposed inherited state. Revoking the agent's session after discovery does not retract the archive. Rotating the token becomes mandatory, and the team now has to identify every place the archive traveled.

A gated executor changes this sequence. The helper script can still run, and the agent can still collect a release status through an approved API action. But it cannot read the token from its environment because the token was never placed there. If the script tries to make a new credentialed call, the executor records it and applies the session or per-call decision. The support archive has less to steal.

Do not grant a bypass merely because an agent claims it needs a tool to complete a task. Ask for the smallest operation the tool needs. If it needs to call one API endpoint, expose that operation. If it needs one deployment command, expose that command. If its need is genuinely broad, treat the request as broad authority and require a human to use a separate, deliberate workflow.

Build the boundary around actions that matter

Inject HTTP credentials privately
Bearer, basic, and custom-header credentials stay in the vault while Sallyport injects them into HTTP calls.

Start with the credentials whose loss would force an emergency rotation or permit a meaningful production change. You do not need to move every development token on day one. A partial boundary around the most dangerous credentials beats a complete migration that nobody finishes.

First, inventory how agents acquire authority today. Search agent launch scripts, shell configuration, CI handoff files, tool configuration, local .env files, and command wrappers. Mark every place that gives an agent a token, password, private key, cloud session, or forwarded authentication socket. This inventory often reveals that the agent has more authority through inherited developer settings than anyone intended.

Then replace plaintext delivery with action requests. Define credential references by purpose, not by person or by a vague environment label. staging-deployer explains more than shared-key-2. Create separate references when two uses need different approval requirements or destinations.

For each reference, decide the following before you automate it:

  • Which HTTP hosts, methods, and paths or which SSH hosts, users, and commands fit its job?
  • Does a new agent process need an explicit approval before it can use it?
  • Does each use need confirmation because the action is destructive or hard to reverse?
  • Which result fields does the agent need back to continue work?
  • Which records will let you explain the action later without retaining secret material?

Test the rejection paths as carefully as the happy path. Run an agent process without approval and confirm that it cannot act. Try a lookalike host name, a redirect to a different host, a command outside the expected deployment path, and an attempt to print the credential reference as though it were a value. Confirm that a revoked session cannot continue to make requests.

Also test the human side. If approvals arrive so often that you stop reading them, your scopes are wrong. If a dialog cannot tell you what process made the request, your process identity is too weak. If the agent cannot finish normal work without repeatedly asking for a generic escape hatch, the action interface needs another carefully bounded operation, not a secret dump.

The standard for success is simple: an agent can accomplish approved work, but a transcript, subprocess, plugin, or poisoned instruction cannot turn that work into possession of a reusable credential. Build toward that standard before the next urgent deployment makes the convenient exception look harmless.

FAQ

Why can't I just give my AI agent read access to a secret manager?

A secret manager protects credentials at rest and controls who may retrieve them. It becomes insufficient when the agent can retrieve the plaintext value, because that agent process can pass the value to another tool, print it, write it to a file, or send it to an unintended host. For autonomous work, prefer a boundary that performs the approved request while withholding the credential.

What is credential injection for AI agents?

Credential injection means a trusted local component adds the needed authentication data to a request after it has approved the action. The agent supplies the method, destination, and request details, but it never receives the bearer token, password, private key, or custom authentication header. This reduces the number of places where a copied secret can escape.

Is it safe to let an agent make API calls with injected credentials?

It can be, if the approving component validates the exact destination, authentication method, and request shape before it connects. A gateway that only lets an agent name a credential still leaves room for host confusion and overly broad calls. The approval record should show where the request went and what authority the process used.

Does SSH agent forwarding keep private keys safe from AI agents?

SSH agent forwarding solves a different problem: it lets a remote host ask your local agent to sign authentication requests. If that remote host is compromised or untrusted, it can use the forwarded agent while the connection remains open. Do not treat forwarding as a general approval mechanism for autonomous agents.

Are API keys and SSH keys different security problems?

An API token is often a bearer credential, so possession is enough to use it. An SSH private key usually proves possession by creating signatures, but the process holding the key can still authenticate as you to approved or unapproved destinations. The formats differ; the need to control each action does not.

Should every AI agent tool call require human approval?

Approve the identity of the agent process and the scope of its run, then require an extra confirmation for credentials that can cause serious damage. A repeated prompt for every harmless request trains people to click without reading. A permanent approval for a vague agent identity gives away too much authority.

What should I log when an AI agent uses credentials?

A useful audit record captures the agent run, the operating system process identity, the destination, action type, credential reference, outcome, time, and approval decision. It should avoid storing secret values and avoid logging sensitive request bodies by default. Tamper evidence matters because a plain local text log cannot settle a dispute after the fact.

Can an agent use a credential without ever seeing it?

No. A gateway has to know enough to make a correct connection, but that is different from exposing the secret to the agent. It can retain the token or private key in a protected store, inject it into an outbound HTTP request, or use it to sign an SSH authentication exchange and return only the command result.

How do I limit what an AI agent can do over SSH?

A command that accepts arbitrary arguments can still do damage after authentication succeeds. Limit the reachable hosts and accounts, separate deploy credentials from administrative credentials, and use remote-side controls such as forced commands or restricted accounts where they fit. The credential boundary should complement those controls, not replace them.

What is the first practical step to stop agents reading secrets?

Move the credentials that agents use most often into an action gateway first, especially deployment tokens, issue tracker tokens, and SSH keys used against production-adjacent systems. Keep the agent's own environment free of secret values, require a new-process approval, and review the first week of action records for surprising destinations or commands. If you find one, reduce the credential scope before you automate more work.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov