SSH certificates for AI agents and local credential custody
SSH certificates for AI agents reduce the exposure of a stolen credential, but only with sound renewal, server checks, local custody, and revocation plans.

SSH certificates and local credential custody work together. A certificate gives an SSH server a time-bounded statement about a public key. Local custody keeps the matching private credential out of an AI agent's context, files, subprocesses, and logs. Treating either one as a substitute for the other leaves a gap that shows up at the worst possible time.
The popular version of this design says that a 15 minute certificate makes everything safe. It does not. If the agent can read the private key, it can keep using that key until the certificate expires, ask for another certificate if it can reach issuance, or leave a copy in a workspace that survives the run. Short validity reduces the duration of server acceptance. It does not remove the capability from the process that holds the secret.
For agents that can change infrastructure, use certificates to reduce the accepted lifetime and use local custody to prevent private credential disclosure. Then design renewal, interruption, and revocation as one operational system. The details matter more than the certificate lifetime printed on a diagram.
A certificate limits acceptance, not possession
An OpenSSH user certificate is a signed public key plus constraints. The SSH server verifies the CA signature and checks fields such as the certificate type, validity interval, principal list, critical options, and extensions. The client still must prove it controls the private key corresponding to the certified public key.
That distinction gets blurred constantly. A certificate is public material. You can place id_ed25519-cert.pub beside a private key, copy it to a client, and inspect it freely. The secret is still id_ed25519. If an agent receives that private file, short certificate validity only limits how long one issued certificate works. The agent still holds a reusable signing credential.
OpenSSH documents the certificate format in PROTOCOL.certkeys and exposes the usual creation controls in the ssh-keygen manual. The signing command takes a CA private key with -s, an identity string with -I, allowed principals with -n, and a validity specification with -V. Those are authorization inputs, not decorative metadata. A server must be configured to trust the CA and interpret the principals before any of them affect access.
Consider two designs:
- In the first, an agent receives
id_ed25519, a certificate, and the CA endpoint. The agent can sign SSH challenges itself and may be able to make future certificate requests. - In the second, a local executor holds
id_ed25519in protected storage. The agent requests a named SSH action, and the executor handles the SSH authentication without returning private material.
Both designs can use the same certificate. Only the second stops an agent prompt injection, a compromised plugin, or an overly curious subprocess from copying the private credential.
Do not use a certificate identity field as proof of custody either. The -I value helps humans correlate issuance and access records, but any caller that can request certificates may choose an impressive-looking identity. Bind issuance to a known public key and a known agent session. If your issuer accepts arbitrary public keys and arbitrary identity strings, your audit trail will become fiction under pressure.
Local custody changes the failure you must plan for
Keeping a credential local changes a secret-exfiltration incident into an action-authorization incident. That is a better problem, but it is still a problem. The agent may ask for a destructive command, target the wrong host, or use an approved session longer than the operator intended.
The local component should hold the private SSH credential and perform the protocol operation itself. The agent should receive command output, exit status, and limited diagnostic information. It should not receive the private key as text, a temporary file path, an environment variable, an SSH agent socket it can forward, or a pretend redacted value that another tool can resolve.
A common bad compromise mounts a secret directory read-only into the agent environment. Read-only protects against edits, not reads. Another bad compromise puts the private key in an SSH agent and gives every child process access to SSH_AUTH_SOCK. That may be acceptable for a tightly controlled interactive shell, but an autonomous coding agent spawns tools, test runners, package hooks, and helper processes. Each one expands who can ask the agent to sign.
Local custody also gives you a useful point for human control. A person can approve the first action from a new process, demand approval for sensitive credentials on every use, or lock the vault entirely. Those controls do not replace server authorization. They decide whether a process gets to attempt an action at all.
Keep this boundary narrow. The component holding the private key should not accept arbitrary shell text and blindly run it because the agent supplied it. It should know the host, target account, and action request it will execute, and it should record those facts before or alongside execution. If you cannot reconstruct which process asked for ssh deploy@host, local custody has become a secret wrapper with poor accountability.
The practical consequence is straightforward: certificate renewal should refresh public authorization, while local custody continues to control private signing. Do not rebuild the private credential every time a certificate expires unless you have a separate reason to rotate it.
Certificate lifetime should follow interruption tolerance
Choose validity based on how long you can tolerate an already issued credential after you halt new work. The right answer differs for a read-only diagnostic run, a deployment workflow, and an agent that can alter access controls.
Start with a duration that leaves room for real work. Certificate renewal fails more often than teams expect because laptops sleep, VPN routes change, the CA is briefly unavailable, a long command holds a connection open, or the machine clock drifts. A very short lifetime turns ordinary interruptions into a constant source of retry logic and tempted bypasses.
For many autonomous tasks, a certificate valid for 15 to 60 minutes is a reasonable first setting. Use the lower end where the account can affect production systems. Use a longer window only when the job truly needs it and you can state why. A multi-hour certificate for a job that runs for ten minutes is usually convenience disguised as operational necessity.
Do not confuse certificate validity with SSH connection lifetime. SSH authenticates when it establishes a connection. A server normally does not eject an already authenticated session when the certificate reaches its expiry time. Connection multiplexing can make this more surprising: a later command may reuse an existing authenticated master connection rather than perform a fresh certificate check.
That behavior changes the design:
- Set certificate validity to bound new authentications.
- Bound command or session duration separately in the local executor.
- Avoid handing an agent a reusable multiplexed control socket.
- Terminate active sessions during an emergency when the action itself must stop.
The OpenSSH ssh_config option ControlMaster is useful for interactive speed, but it undermines simple assumptions about per-command authentication. For autonomous work, disable multiplexing for sensitive targets or make the executor own and tear down the connection. Do not let an agent inherit a control path that happens to stay authenticated after its approval or certificate window should have ended.
Clock discipline also belongs in this section. A certificate has an absolute validity interval. If the issuing host and target server disagree materially on time, a freshly minted certificate can look expired or not yet valid. Monitor time synchronization on both sides, and fail closed when the server rejects the validity window. Do not work around a clock failure by issuing a much longer certificate.
Renewal must prove the same custody boundary still exists
A renewal service should issue a new certificate only after it can tie the request to the locally held credential, the current agent session, and the requested authorization scope. Merely accepting a public key in an HTTP request proves nothing about who controls the matching private half.
The cleanest flow asks the local executor to prove possession of its registered public key during renewal. The issuer verifies that proof, checks the active session and requested principal, signs the public key, and returns only the public certificate. The executor attaches that certificate to its internally held private credential for the next SSH connection.
A certificate creation command might look like this on the issuer:
ssh-keygen -s agent_user_ca -I run-4821 -n deploy-prod -V +30m agent-run-4821.pub
This command signs agent-run-4821.pub with the CA private key. It creates a companion public certificate, usually named agent-run-4821-cert.pub. The run-4821 string helps correlate records, deploy-prod is the allowed principal, and +30m requests a thirty minute interval. The issuer should generate that identity string itself rather than trust an agent-provided label.
Inspect every new certificate before you let automation depend on it:
ssh-keygen -L -f agent-run-4821-cert.pub
The output lists the certificate type, signing CA fingerprint, identity, serial, validity range, principals, critical options, and extensions. Make this inspection part of tests for your issuer. A missing principal, a validity range that starts in the future, or an unexpected extension will otherwise surface as a failed deployment with somebody reaching for a long-lived fallback key.
Renew before the work needs a new SSH connection, not at the exact expiry second. The executor can request a replacement near the end of the interval while the existing certificate still works. It should also refuse to start a command if too little certificate lifetime remains for the command's allowed duration. That check prevents a job from beginning an important write just before the next authentication fails.
Keep renewal idempotent at the request level. A lost response can make a client retry even though the issuer already created a valid certificate. Record a request identifier, session identifier, public-key fingerprint, principal, and expiry. On a retry, return the previously issued certificate when the inputs match. If they differ, reject the request rather than guessing which request the agent meant.
Principals and server policy decide what the certificate can do
A certificate says who may authenticate. The server still decides which local account accepts that identity and what that account can do. If you allow an agent certificate to authenticate as a broad shared administrator account, short validity will not rescue a loose authorization decision.
OpenSSH can trust a user CA through TrustedUserCAKeys. You can then map accepted principals with AuthorizedPrincipalsFile or AuthorizedPrincipalsCommand. The latter is useful when a server needs a centrally managed account mapping, but it adds an availability dependency to login. A static principals file is less flexible and often easier to reason about for a small fleet.
A constrained server configuration can look like this:
TrustedUserCAKeys /etc/ssh/agent_user_ca.pub
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
RevokedHostKeys /etc/ssh/revoked_agent_credentials.krl
For a local account named deploy, /etc/ssh/auth_principals/deploy might contain only:
deploy-prod
That layout means the server accepts a certificate from the named CA only when it contains deploy-prod as a principal and has not been revoked by the configured KRL. Test the exact version of OpenSSH on your servers. Configuration directives and certificate behavior are stable in principle, but distribution packaging and older releases can affect what you can rely on.
Certificate critical options and extensions can narrow use further. OpenSSH supports critical options such as force-command and source-address; it also recognizes extensions such as permit-pty, permit-port-forwarding, and permit-agent-forwarding. Use these when the target workflow has a narrow shape. An agent that only runs a deployment helper should not receive a general interactive shell by accident.
Do not put a generic permit-pty capability on an agent certificate because someone may need debugging later. Issue a separate, consciously approved diagnostic credential when you need one. Convenience extensions tend to remain long after the exceptional task that justified them.
Emergency revocation needs more than waiting for expiry
Short validity handles routine containment. If you stop issuance and the certificate expires in thirty minutes, new SSH authentications stop after that window. That may be adequate when you caught an errant task before it touched a sensitive target. It is inadequate when a private credential may have escaped or an agent has begun harmful work.
Your first containment move is to stop new issuance for the relevant session, credential, or CA. Then prevent already issued credentials from authenticating where the urgency warrants it. In OpenSSH, a Key Revocation List, or KRL, gives servers a way to reject specific certificates, public keys, or a signing CA.
To add an issued certificate to a KRL, an operator can use a command shaped like this:
ssh-keygen -k -f revoked_agent_credentials.krl -s agent_user_ca.pub agent-run-4821-cert.pub
The -s argument identifies the CA that signed the certificate. Distribute the resulting KRL to the target servers, keep the path consistent with RevokedHostKeys, and reload sshd according to your operating procedure. Test the full path in advance: create a certificate, authenticate successfully, add it to the KRL, distribute the file, reload sshd, and verify that a new authentication fails.
A KRL is not a central kill switch. Every SSH server must receive the file and read it. If a host is unreachable, offline, or managed under a separate configuration regime, it may keep accepting the certificate until expiry. That is why a short validity interval remains useful even when you operate revocation lists.
When you suspect the CA private key itself, revoking individual certificates is the wrong scale of response. Remove or replace trust in that CA on affected servers, issue a new CA, and recertify only the credentials you still trust. Keep a distinct CA for agent access so this operation does not lock out human emergency access. Maintain a tested break-glass path, but keep it outside the agent's reach and record its use.
Finally, kill active sessions. KRL distribution blocks future authentication; it does not necessarily terminate an SSH connection that the server already accepted. Use your host session controls, job supervisor, or network controls to stop live work. Then review commands already executed, because credential revocation cannot undo a deployment or restore a deleted file.
A certificate issuer must fail closed during uncertainty
The issuer should reject renewal when it cannot verify the local executor's identity, the session state, the requested principal, or the authorization required for that scope. Availability pressure makes teams turn these checks into warnings. That decision converts a temporary outage into unbounded credential issuance.
Plan for the ordinary failure cases before deployment day. If the issuer is unreachable, let an existing certificate work until its normal expiry, but do not silently substitute a static private key. If the local vault is locked, deny the action. If a human approval is required and nobody responds, expire the work rather than allowing the agent to retain an approval indefinitely.
A useful renewal record includes the CA fingerprint, certificate serial, issuance time, expiry time, public-key fingerprint, requested principal, agent process identity, session identifier, target class, and approval result. The certificate itself contains some of this information, but it does not tell you whether the request followed your intended local control path.
The issuer should also prevent scope drift across renewal. A session approved for deploy-prod should not renew into root-prod, a broader principal, merely because the agent revised its plan. Require a new authorization boundary for a different account, target group, or command class. This is where long-running autonomous jobs often become unsafe: the session starts narrow, then accumulates exceptions through renewal logic nobody reviews.
Use separate records for issuance and execution. Issuance proves that the CA authorized a certificate. SSH server logs and executor activity records prove where a credential was used and which command ran. You need both when an operator asks whether a certificate was misissued or correctly issued but misused.
Test revocation as a timed operational drill
A runbook that says revoke the certificate is incomplete until it names the systems, files, access rights, expected failure, and person who can act when the issuer is down. Run the drill against a nonproduction target, with real certificates and the same distribution mechanism you use in production.
Use this sequence:
- Issue a certificate with a known serial and a short, documented interval.
- Authenticate once and record the server-side acceptance event.
- Disable renewal for the associated agent session.
- Add the certificate to the KRL, distribute it, and reload the target sshd instances.
- Attempt a new SSH connection and confirm that the server rejects it, then terminate the original live session if one exists.
Measure the actual time from the containment request to rejection on each server group. Do not publish a target number you have never observed. The number depends on how quickly your configuration system delivers the KRL and how reliably it reloads sshd.
Also test the uncomfortable cases. Revoke a certificate when one target host is unreachable. Revoke during an existing multiplexed connection. Revoke while the local machine is asleep and renewal cannot complete. Revoke after an operator has approved a session but before the first command. Each result tells you whether your controls block issuance, new authentication, or active execution. They are different controls, and calling all of them revocation creates dangerous assumptions.
Store the evidence from the drill. Keep the issued certificate, the KRL update, the executor record, and the target server's rejection log. When a real incident happens, that packet gives responders a verified command path instead of a document that has aged into wishful thinking.
Keep approval, audit, and SSH access connected
Human approval is useful only when the approval maps to the process that will use it. Approving an unnamed background process is an invitation to approve the wrong thing. The reviewer should see who signed the process, what session it is starting, and whether the requested SSH credential carries an ordinary or sensitive scope.
Sallyport keeps SSH credentials in its encrypted local vault and executes SSH through its bundled helper, so an MCP-capable agent does not need the private SSH key in its own environment. Its session and activity journals can connect a human approval with the agent process and subsequent action records.
The audit trail needs tamper evidence as well as convenient viewing. Sallyport projects its journals from an encrypted, hash-chained audit log, and sp audit verify lets an operator verify that chain offline without needing vault access. Run that verification during incident review and when you export evidence; a readable activity list alone does not prove that nobody removed an inconvenient event.
Do not make the audit system responsible for access policy it does not enforce. The SSH server must still trust the right CA, accept only intended principals, read the current KRL, and restrict the target account. The local executor must still protect private credentials and ask for the approvals you chose. Logs give you evidence and operational feedback. They do not repair an account with excessive permissions.
The strongest starting move is not a smaller certificate duration. Inventory which agent actions need SSH, give each one a narrow principal and account, place the private credential behind local custody, then rehearse the path that halts issuance, distributes revocation, and ends live sessions. Once that works, choose the shortest certificate lifetime your actual renewal system can sustain.
FAQ
Do SSH certificates replace private SSH keys?
They solve different problems. A certificate tells an SSH server which public key it may accept, for which principals, and until when. A local custody boundary prevents the agent from reading or exporting the private credential that produces the SSH signature.
How long should an SSH certificate last for an AI agent?
For an autonomous agent, 15 to 60 minutes is often a sensible starting range if renewal is reliable. Use a shorter period when the agent can reach production or makes unrestricted changes. Do not choose a five minute lifetime until you have tested renewal during slow jobs, sleep, and network interruptions.
Can I revoke an SSH certificate before it expires?
No. OpenSSH does not magically distribute revocations to every server. A KRL only works after each relevant SSH server receives it and sshd uses it through RevokedHostKeys, while a short certificate lifetime still limits the remaining window if distribution lags.
Should AI agents use a separate SSH certificate authority?
Use a separate SSH CA for autonomous agents whenever their access differs from human access. Separate CAs make an emergency response less destructive because you can stop trusting the agent CA without breaking every engineer's certificate. They also make audit review less ambiguous.
Is a short-lived SSH certificate safe if the agent has the private key?
Usually no. An SSH certificate proves that a CA signed a public key, but the private half can still be copied, reused, or handed to another process. Keep the private credential in local custody and let a trusted executor perform the signing operation instead of exposing the key to the agent.
Can an agent send its own public key to the SSH CA?
The public key is not secret, but it identifies the credential that the CA should certify. Letting an agent create arbitrary public keys gives it a path to request certificates for identities you may not be able to tie back to one locally held credential. Bind issuance to a registered public key or a proof of possession from the local executor.
What should I log when issuing SSH certificates to agents?
At minimum, record the CA identity, certificate serial, certificate identity field, principals, validity interval, destination host, account, session identifier, and the approval that allowed the action. Retain the issuance record separately from SSH server logs. Server logs explain what the account did; the issuance record explains why that credential existed.
Should I use short certificate lifetimes or a KRL?
A short validity window is usually simpler and more dependable than a KRL during normal operations. Keep a KRL for urgent cases where waiting for expiry is unacceptable, but treat its distribution as production infrastructure. Test it on every server group before depending on it in an incident.
What SSH certificate principal should an AI agent use?
Use the exact principal needed by the target account and avoid broad shared principals such as deploy unless every holder has identical authority. On servers that support it, map principals to local accounts through AuthorizedPrincipalsFile or AuthorizedPrincipalsCommand. The principal is authorization input, not a friendly label.
What is the first action after an AI agent credential is compromised?
Stop new certificate issuance first, then remove the affected CA or certificate trust from the servers that matter. Publish a KRL if you need an immediate block, revoke the agent session in its local executor, and inspect activity records for already completed commands. Rotating the locally held private credential comes after containment, not before it.