A leaked SSH private key opens every account whose authorized_keys file trusts it, on every host, until each copy of the public key is removed or revoked, and nobody usually knows how many places that is. SSH keys are non-human identities in their own right: they rarely expire, they are copied between servers and pipelines, and one key often unlocks accounts that hold further keys, creating chains of trust. This playbook template gives [Organisation] a response for a suspected or confirmed SSH private key compromise, whether the key belonged to a person, a service account, a deploy pipeline or a certificate authority. It covers the authorized_keys sweep, mapping the trust graph, revocation with OpenSSH key revocation lists (KRLs) and certificates, and rotation at scale. It is for incident responders, Linux and platform engineers and NHI owners. Replace every [placeholder] before use; for day-to-day key hygiene see the SSH Key Management Guide.
How to use this template
- Adopt it as a sub-playbook of the Incident Response Plan Template and link it from the Leaked Credential Response Playbook.
- Pick the track. Track K covers plain public keys in authorized_keys files. Track C covers OpenSSH certificates signed by a user or host CA. Many estates have both.
- Prepare the tooling now: a way to read authorized_keys and sshd configuration from every host (configuration management, EDR or an inventory tool), a central KRL distribution path, and a list of SSH CAs and who holds their keys.
- Record decisions. Each "Decision" line names who decides and on what evidence.
1. Scope and triggers
1.1 This playbook applies to any SSH identity key (private key) or SSH CA signing key used to access [Organisation] systems, including keys on laptops, jump hosts, CI/CD runners, configuration management servers and code hosting deploy keys.
1.2 Techniques to record in the case:
| ATT&CK technique | How it shows up in an SSH incident |
|---|---|
| T1552.004 Unsecured Credentials: Private Keys | Private key found in a repository, image, backup, paste site or infostealer log |
| T1021.004 Remote Services: SSH | The stolen key used to log in and move between hosts |
| T1098.004 Account Manipulation: SSH Authorized Keys | Attacker adds their own public key to authorized_keys for persistence |
| T1563.001 Remote Service Session Hijacking: SSH Hijacking | Attacker on a host abuses a forwarded agent or existing session to reach further hosts |
1.3 Triggers: a private key reported by secret scanning or a third party; a known key fingerprint authenticating from an unapproved source address; a new, unexplained line in an authorized_keys file; a laptop, runner or server holding keys confirmed compromised; a departing administrator who held shared keys; a CA signing key exposed. NIST IR 7966 notes that an authorised key used from an unapproved host suggests leaked credentials and calls for immediate rotation.
1.4 A recent case: in the Cursor marketplace extension compromise, the tampered extension tried to add an attacker SSH key to the user's authorised keys and open a reverse SSH tunnel, which is why the sweep in section 3 looks for added keys as well as stolen ones.
2. Roles and contacts
| Role | Named lead | Responsibilities |
|---|---|---|
| Incident lead | [Name, phone] | Declares the incident, owns the decision log and the rotation plan |
| Key owner | [From NHI or key inventory] | Confirms what the key was for, where it lived and what depends on it |
| Platform or Linux engineering | [Rota] | Runs the sweep, deploys KRLs and new keys, edits sshd configuration |
| CA custodian | [Name, deputy] | Revokes certificates, rotates the CA key if needed, re-issues certificates |
| Code platform administrator | [Name] | Removes user SSH keys and deploy keys on the code hosting platform |
| SOC analyst | [Rota] | Log searches by fingerprint and source address, scoping lateral movement |
2.1 A key with no owner is a finding in itself. Record it and assign one before rotation; see the Service Account Security Guide.
3. Detection and triage
3.1 Identify the key exactly. Take the public key or derive it from the leaked private key, and record its SHA256 fingerprint (ssh-keygen -l prints a key's fingerprint, SHA256 by default). Every later search uses the fingerprint, not the file name or comment, because comments can be anything.
3.2 Sweep for the public key. Search every host for the key in each user's authorized_keys files. Check sshd_config first: AuthorizedKeysFile defaults to .ssh/authorized_keys and .ssh/authorized_keys2 but may point elsewhere, and AuthorizedKeysCommand may fetch keys from a directory or other source that a file search will miss. Include root, service accounts, container images, golden images and backups that will be restored later.
3.3 Map the trust graph. For every account that trusts the key, ask: which private keys does that account itself hold, and where are their public keys trusted? NIST IR 7966 describes this transitive trust: an attacker who compromises one host can pivot along key-based trust to others. Record the result as a table:
| Hop | Host and account trusting the key | Restrictions on the line | Keys held by that account | Where those keys are trusted |
|---|---|---|---|---|
| 1 | [host / account] | [from=, command=, restrict, none] | [fingerprints] | [hosts / accounts] |
| 2 | [...] | [...] | [...] | [...] |
3.4 Read the restrictions. An authorized_keys line with from="pattern-list" accepts the key only from matching hosts or addresses; command= forces a single command; restrict disables port, agent and X11 forwarding, PTY allocation and ~/.ssh/rc. A restricted line lowers severity; an unrestricted line on a privileged account raises it.
3.5 Search authentication logs for the fingerprint and for the accounts in the trust graph, at least [90] days back. OpenSSH logs accepted public key logins with the key type and fingerprint; confirm your LogLevel and log forwarding actually capture them, and note that the FingerprintHash setting controls which hash is logged.
3.6 Look for attacker keys. Compare every authorized_keys file in the trust graph with the last known good state from configuration management. Any key nobody can explain is treated as attacker persistence (T1098.004).
3.7 Severity: Critical: CA key, or a key trusted by root or a privileged automation account, or evidence of use. High: key trusted on production hosts without from= restrictions. Medium: tightly restricted key with no sign of use.
3.8 Decision (incident lead): confirm compromise and contain, or record evidence (for example, the "leaked" key is a public key only) and close.
4. Containment
4.1 Track K: revoke centrally first, then remove. Add the compromised key to the file named by sshd's RevokedKeys option on every host. OpenSSH refuses keys listed there for public key authentication; note that if the file cannot be read, public key authentication is refused for all users, so deploy it carefully. A KRL generated with ssh-keygen -k can revoke plain keys by the key itself or its SHA256 hash; ssh-keygen -k -u adds entries to an existing KRL, and ssh-keygen -Q tests keys against it.
4.2 Track K: remove the line from every authorized_keys file found in 3.2, through configuration management so it is not re-added at the next run. Removal without the KRL leaves gaps where the sweep missed a copy; the KRL without removal leaves clutter that hides the next attacker key.
4.3 Track C: revoke the certificate. A KRL can revoke certificates by serial number or serial range, or by key ID, when generated with the CA key given via -s. This is why every certificate needs a unique serial and a meaningful key ID set with -I, which the server also logs.
4.4 Track C: if the CA signing key is compromised, remove it from TrustedUserCAKeys (or cert-authority lines) on every server, and for host CAs mark it @revoked in known_hosts files, which ssh never accepts. Every certificate it signed becomes untrusted at once, so stand up the new CA first.
4.5 Code platforms: delete the key from user accounts and deploy keys. GitHub documents that deploy keys are attached to a repository, not a person, so removing the user who created one does not revoke it, and that deploy keys have no expiry date. See How to find unused GitHub deploy keys and SSH keys.
4.6 Isolate hosts where attacker activity is confirmed, and stop agent forwarding to and from them.
4.7 Decision (incident lead and key owner): revoke now or rotate first. Revoking an automation key before its replacement is deployed stops the workload. Revoke immediately when there is evidence of use; otherwise deploy the replacement within [hours] and revoke straight after.
5. Eradication
5.1 Remove every unexplained key found in 3.6 and investigate how it was added.
5.2 Rotate every private key held by accounts the attacker could reach in the trust graph, not only the leaked one. NIST IR 7966 recommends rotating all identity keys during remediation and rotating group-held keys when a member leaves.
5.3 Find and delete every copy of the leaked private key: repositories (including history), images, backups, CI variables, shared drives. See the Git Secret Scanning Guide.
5.4 Break inappropriate chains. NIST IR 7966 advises against giving one account both incoming and outgoing key-based trust; separate them, with approved exceptions such as a jump server.
6. Recovery
6.1 Issue replacement keys with restrictions from the start: from= for automation, command= where one action is needed, restrict by default, and expiry-time="YYYYMMDD" on plain keys so they stop being accepted after a date.
6.2 Prefer certificates for the replacement. With a user CA in TrustedUserCAKeys, short validity (ssh-keygen -V) and principals checked against the username or AuthorizedPrincipalsFile, the next compromise expires on its own. See the SSH Protocol and Certificate Authority Guide.
6.3 Confirm that workloads authenticate with the new keys, that the old fingerprint appears in no authorized_keys file, and that it is rejected (ssh-keygen -Q against the KRL). Monitor for the old fingerprint for [30] days.
6.4 Rebuild golden images and backups that contained the old key or line, or record a step to remove it on restore.
7. Communication and notification
| Audience | Owner | When |
|---|---|---|
| Key owners and service teams in the trust graph | Incident lead | At containment, with the rotation schedule and any expected outage |
| Executive sponsor | Incident lead | At declaration for Critical |
| Customers or partners whose systems trusted the key | Account owners, legal | As soon as their exposure is confirmed, so they can revoke on their side |
| Data protection regulator | Privacy lead | If personal data was accessed and the breach is notifiable; under GDPR Article 33, without undue delay and where feasible within 72 hours of becoming aware |
8. Post-incident review
8.1 Record: when the key leaked, when it was first used, detection, full revocation and last rotation. Revocation is complete only when every copy found in the sweep is gone.
8.2 Ask: did the inventory list every host trusting the key? Were lines restricted? Why was a long-lived key used where a certificate could be? The Zeabur second breach shows what happens when a post-incident rotation misses credentials: long-lived admin tokens left out of the first rotation were used two months later.
8.3 Track: hosts covered by the authorized_keys inventory (target 100%), keys without an owner, unrestricted keys on privileged accounts, time from detection to KRL deployment, and the share of SSH access using certificates.
Implementation guidance
- Build the authorized keys inventory before you need it. NIST IR 7966 treats a complete inventory of identity keys as infeasible but a complete inventory of authorised keys, with source and command restrictions, as the practical goal, and says automation is effectively required at enterprise scale.
- Deploy an empty KRL everywhere now. Set RevokedKeys in sshd_config fleet-wide with a managed, readable file. During an incident you only add entries; you do not change sshd configuration under pressure.
- Centralise where keys are read from. Moving AuthorizedKeysFile to a root-owned path, or using AuthorizedKeysCommand, stops users and attackers adding keys in home directories and makes the sweep a single query.
- Move to certificates in phases: host certificates first, then user certificates for people, then automation, keeping plain keys only with expiry-time and from=. Test with a pilot group and keep the old key path until the pilot is stable, so rollback is a configuration change.
- Size it. A small team with tens of servers can sweep with configuration management and a spreadsheet trust map. A large estate needs a key discovery tool, a CA with short-lived certificates, KRL distribution and SIEM alerts on unknown fingerprints.
- Test the playbook each year by "leaking" a test key trusted on a few hosts and timing the sweep, KRL deployment and rotation.
| Playbook element | NIST CSF 2.0 | NIST SP 800-53 Rev. 5 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|
| Key inventory and ownership | PR.AA-01, ID.AM-08 | IA-5, AC-2 | A.5.16, A.8.24 |
| Restricted keys and certificates | PR.AA-05 | IA-5(2), AC-17 | A.8.5 |
| Fingerprint logging and alerting | DE.CM-03 | AU-12 | A.8.15, A.8.16 |
| Triage, revocation and rotation | RS.MA-02, RS.MA-01 | IR-4, SC-12 | A.5.26 |
| Review | RS.AN-03 | IR-4 | A.5.27 |
References: NIST: IR 7966, Security of Interactive and Automated Access Management Using Secure Shell (SSH) · OpenBSD: sshd_config(5) manual · OpenBSD: ssh-keygen(1) manual, key revocation lists · OpenBSD: sshd(8) manual, authorized_keys and known_hosts format · GitHub Docs: Managing deploy keys · GitHub Docs: Reviewing your SSH keys · MITRE ATT&CK: T1098.004 SSH Authorized Keys
This template is provided for guidance and must be adapted to your organisation. Related NHI Mgmt Group resources: SSH Key Management Guide · SSH Protocol and Certificate Authority Guide · OpenSSH Server Hardening Guide · Secrets Management Guide · Cryptographic Key Management Guide