Join our Newsletter — 33% off our NHI Course
Home› Guides› How to find unused GitHub deploy keys and…
Guide Non-Human Identity (NHI)

How to find unused GitHub deploy keys and SSH keys: inventory, last use and safe removal

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 28 October 2026 13 min read
On this page

GitHub deploy keys and user SSH keys never expire, are often added once by someone who has since moved on, and keep working until a person deletes them. This guide shows how to list every deploy key in an organisation with its last use date, how to see the SSH keys members have authorised for a SAML-protected organisation, how to separate keys that are idle from keys that are quietly running deployments, and how to remove them without breaking a release. It is for GitHub organisation owners, platform engineers and identity teams running an NHI inventory or cleaning up after an incident. For the wider GitHub picture see the GitHub and GitHub Actions Security Guide, and for key lifecycle in general the SSH Key Management Guide.

Key takeaways

  • Deploy keys belong to the repository, not a person: GitHub states that if the user who created a deploy key is removed from the repository, the key stays active, so offboarding a person does not remove the keys they added.
  • The REST API gives you last use: the deploy key object returned by GET /repos/{owner}/{repo}/keys includes last_used, added_by, read_only and enabled, which is enough to triage most keys without touching servers.
  • Write access is close to admin: GitHub documents that a deploy key with write access can perform the same actions as an organisation member with admin access on that repository, so write keys come first.
  • User SSH keys are visible to owners only through SSO: in GitHub Enterprise Cloud with SAML SSO, the credential authorisations API lists SSH keys members have authorised, with a fingerprint, authorisation date and credential_accessed_at.
  • Deletion is final, so stage it: a deleted deploy key cannot be restored, only replaced with a new key pair, so confirm the consumer before deleting and keep the replacement ready.

When you need this

  • NHI inventory or access review: deploy keys are machine credentials with no owner field beyond added_by, so they are easy to miss in a people-centred review. See the NHI Inventory Reconciliation guide.
  • Leaver or team change: the person who added a key leaves, but the key, and the server holding its private half, remain.
  • Server or build host compromise: GitHub's own guidance notes that deploy keys are usually not protected by a passphrase, so the private key is easily reached if the server is compromised. Every key whose private half sat on that host is in scope.
  • Leaked key material: keys that do not expire stay valid for as long as nobody deletes them. In the leaked GitHub App private keys research, 474 leaked App keys still authenticated, some years after they leaked. Deploy keys have the same property.
  • Policy change: before an enterprise owner restricts deploy keys, because GitHub warns that disabling the policy disables existing deploy keys in every repository in the enterprise.

Before you start

  • Roles: repository admin to list and delete deploy keys; organisation owner to read SAML credential authorisations and the organisation audit log; enterprise owner for the enterprise deploy key policy.
  • Plan: the audit log REST API and the credential authorisations API require GitHub Enterprise Cloud. On GitHub Enterprise Server, the same deploy key endpoints exist on your instance hostname.
  • A read-only token: use a token that can read repository administration data for the organisation, ideally a short-lived one created for the audit, and record it so you can revoke it afterwards.
  • A findings register: one row per key with repository, key ID, title, fingerprint, created, last used, read only or write, added by, consumer host, owner team and decision.
  • A change window for deletions: discovery is read-only; removal is not, and needs agreement from the team that owns each deployment.

Step-by-step

1. Know which key types you are looking for

Key typeAttached toWhere to list itLast use dataExpiry
Deploy keyOne repositoryRepository Settings > Security > Deploy keys; GET /repos/{owner}/{repo}/keyslast_used field in the APINone
User SSH authentication keyA personal account, access follows the user's repository permissionsThe user's own Settings > SSH and GPG keys; GET /user/keys for the authenticated userlast_used for the key owner onlyNone
SSH key authorised for SAML SSOA personal account, authorised for one organisationGET /orgs/{org}/credential-authorizations (Enterprise Cloud)credential_accessed_atNone on the key itself
SSH certificate from an organisation CAA user, signed by a CA you add to the organisation or enterpriseYour CA's issuance records; CA list in organisation settingsYour CA logs and Git eventsValidity period set at signing
SSH signing keyA personal account, used to sign commitsGET /user/ssh_signing_keysNot providedNone

Signing keys do not authenticate Git operations, so they are out of scope for an access cleanup, but they often get confused with authentication keys in user reports.

2. Enumerate deploy keys across every repository (read-only)

  1. List every repository in the organisation, including archived and private ones.
  2. For each repository, call GET /repos/{owner}/{repo}/keys and page through the results. For a single repository, gh repo deploy-key list --repo OWNER/REPO prints the same keys, and its JSON output exposes id, key, title, createdAt and readOnly.
  3. Record for each key: id, title, created_at, read_only, added_by, last_used, enabled and verified. The API schema marks added_by and last_used as nullable, so a null value is a finding to investigate, not proof the key was never used.
  4. Compute the SHA256 fingerprint of each public key so you can match it to servers later (for example by saving the key value to a file and running ssh-keygen -lf on it).
  5. Flag any fingerprint that appears on more than one repository. GitHub does not let the same deploy key be added to two repositories, so a repeated fingerprint usually means a key was deleted and re-added, or the inventory has mixed in user keys.

3. Classify each deploy key

SignalMeaningDefault decision
last_used older than your idle period (for example 90 days)No deployment has used it recentlyDisable path: confirm owner, then delete
last_used null and created_at oldNever used, or used before the field was populatedTreat as idle, confirm with the repository team
read_only falseCan push, and acts like a repository adminPriority review; justify or replace with read-only or a GitHub App
added_by is a departed userNo living ownerFind the consumer host; assign an owner or delete
enabled falseDisabled, for example by the enterprise policyDelete once nothing depends on re-enabling it
Title gives no host or purposeNobody can tell what breaks if it goesRename on replacement with host, purpose and team
Repository archivedDeployments should have stoppedDelete any remaining keys after a short notice period

4. Review SSH keys authorised for the organisation (Enterprise Cloud with SAML SSO)

  1. Call GET /orgs/{org}/credential-authorizations, or open each member's page under People, where GitHub shows the member's linked identity, active sessions and authorised credentials.
  2. Filter to SSH key credentials. Each entry carries the login, fingerprint, authorized_credential_title, credential_authorized_at and credential_accessed_at.
  3. Flag keys with an old or null credential_accessed_at, keys belonging to members who have left the identity provider, and members holding many keys.
  4. Remember the limit: GitHub explains that to reach organisation resources through Git, a member must use an SSH key authorised for that organisation, so this list covers access to your organisation, but not keys a user holds for their personal repositories.

Without SAML SSO, owners cannot list members' SSH keys with last use. Ask each user to run the self-review GitHub documents (Settings > SSH and GPG keys, compare with ssh-add -l -E sha256 locally, delete unknown keys) and record the result, or require SSH certificates (step 7).

5. Correlate with Git activity before deleting

  • Git events: in Enterprise Cloud, git.clone, git.fetch and git.push events are available only through the audit log REST API, streaming or exports, not in the web interface, and GitHub retains them for seven days. If you need a longer window, stream the audit log to your SIEM before the review.
  • Key lifecycle events: the public_key category records public_key.create, public_key.delete and public_key.update for both user SSH keys and deploy keys, which tells you who added a key and when.
  • Consumer hosts: search configuration management, CI runner images and ~/.ssh/config host aliases for the fingerprints. GitHub's own guidance for multiple repositories on one server uses one key pair and one host alias per repository, so a host alias such as github.com-repo-0 is a strong clue.

6. Remove idle keys (destructive, change-controlled)

  1. Notify the repository team with the key title, fingerprint and last use, and a deletion date.
  2. For keys still in use but over-privileged, create the replacement first: a new read-only deploy key, or a GitHub App installation, which GitHub recommends for finer control. Switch the consumer and confirm a successful clone or deploy.
  3. Destructive: delete the old key with DELETE /repos/{owner}/{repo}/keys/{key_id} or Delete on the Deploy keys page.
  4. Delete the private key from the consumer host, so a host backup cannot bring it back into use if the public half is re-added.
  5. For SSO-authorised user keys, an owner can remove the authorisation with DELETE /orgs/{org}/credential-authorizations/{credential_id}. That ends the key's access to the organisation; the key stays on the user's account.

7. Triage summary

  • Delete now: idle beyond your period, added by a departed user with no consumer found, on an archived repository, or on a host known to be compromised.
  • Replace: write keys that only need to pull, and keys whose private half is shared across hosts.
  • Move: automation that touches many repositories to a GitHub App or short-lived federation. See the GitHub Actions OIDC Federation Hardening Guide.
  • Accept with exception: named owner, documented consumer host, review date and audit log monitoring.

Verify it worked

MetricSourceTarget
Deploy keys idle beyond policylast_used from the deploy keys APIZero after each review
Write-enabled deploy keysread_only false countFalling, each with a documented reason
Keys added by departed usersadded_by joined with directory statusZero without an assigned owner
SSO-authorised SSH keys with no recent accesscredential_accessed_atZero after each review
Key additions outside change controlpublic_key.create audit eventsEvery event matched to a ticket

Re-run the enumeration after the change window and diff it against the register. Watch build and deployment pipelines for authentication failures for a week; a failure that names a deleted key means a consumer was missed.

Rollback and common errors

  • Deletion cannot be undone: rollback means generating a new key pair, adding the public key and updating the consumer. Have the replacement ready before deleting anything in active use.
  • Trusting a null last use: last_used is nullable. Check Git events and consumer hosts before treating a null as never used on a key that guards production deploys.
  • Flipping the enterprise policy first: GitHub warns that changing the deploy key policy to disabled disables existing deploy keys in all repositories and breaks scripts, apps and workflows that use them. Inventory and migrate first, then restrict.
  • Reading Git events too late: Git events are kept for seven days, so a review based on the audit log alone sees only the last week of clones and pushes.
  • Removing a person and assuming their keys are gone: removing the user from the repository does not remove deploy keys they added.
  • Deleting the public key but not the private one: the private key on the host remains a credential if anyone re-adds the public half, and it is still material an attacker can take from a backup.

Hardening afterwards

  • Prefer GitHub Apps or federation: GitHub recommends a GitHub App over deploy keys for finer control of repository access and permissions, and App installation tokens are short-lived. Inventory those Apps too: see How to audit GitHub Apps and OAuth apps in an organisation.
  • Use the enterprise deploy key policy deliberately: under Enterprise Policies > Repositories > Deploy keys, allow, restrict or delegate creation to organisation owners once the migration is done.
  • Require SSH certificates for people: with an SSH certificate authority added to the organisation or enterprise, GitHub validates the certificate's validity period, so access ends when the certificate expires. GitHub gives the example of a daily certificate. See the SSH Protocol and Certificate Authority Guide.
  • Name keys for their consumer: a title with host, purpose and team makes the next review a matter of reading, not investigating.
  • Stream the audit log: keep public_key.* and git.* events beyond seven days and alert on new write-enabled deploy keys. See the GitHub Audit Log Detection Guide.
  • Schedule the export: run the enumeration monthly and feed results into offboarding. See the NHI Offboarding and Decommissioning Guide.
FrameworkReferenceRelevance
NIST CSF 2.0PR.AA-01, PR.AA-05, DE.CM-03Credentials for services managed; access reviewed for least privilege; usage monitored
NIST SP 800-53 Rev 5IA-5, AC-2, AC-2(3), AC-6, AU-6Authenticator management, account review, disabling unused access, least privilege, audit review
CIS Controls v8.1Controls 5 and 6Account management and access control management, including service accounts
MITRE ATT&CK v20T1552.004 Private Keys; T1213.003 Code RepositoriesTheft of private keys from hosts; collection from repositories once a key is taken

Practitioner checklist

  • Enumerate deploy keys in every repository, including archived ones, with last_used, added_by and read_only.
  • Fingerprint every key and match it to a consumer host and owning team.
  • Review every write-enabled deploy key first and justify or replace it.
  • On Enterprise Cloud with SAML SSO, export SSH key credential authorisations and review credential_accessed_at.
  • Stream the audit log so Git events outlive the seven-day retention.
  • Create replacements before deleting keys in use; delete private keys from hosts too.
  • Restrict deploy key creation with the enterprise policy only after migration.
  • Move multi-repository automation to GitHub Apps or federation and require SSH certificates for people where practical.
  • Repeat the review monthly and on every leaver and host compromise.

Standards and references

Related NHI Mgmt Group resources: GitHub Personal Access Tokens Security Guide, How to revoke a leaked GitHub personal access token and check what it touched, SSH Key Management Guide, Service Account Inventory Template.

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 28 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.