← All articles

Secrets in SSH Keys and Deploy Keys: How Private Keys Leak and What to Do About It

July 6, 2026

Why SSH Private Keys Are Among the Most Dangerous Leaked Credentials

Most secret-leakage conversations focus on API tokens and database passwords. SSH private keys and deploy keys rarely get the same attention — but they should. A leaked API token usually grants access to one service. A leaked SSH private key can grant persistent shell access to every server, repository, or deployment target it was registered against, often with no expiry and no audit trail on the client side.

This article covers exactly how these keys end up exposed, how to detect whether yours already have been, and a concrete remediation plan you can act on today.

How SSH Private Keys Actually End Up Leaked

1. Committed Directly to a Repository

The most common path is embarrassingly simple: a developer generates an SSH keypair, saves the private key inside the project directory (e.g., ./deploy/id_rsa or ./infra/keys/prod.pem), and commits it. A missing or misconfigured .gitignore is all it takes. Because private key files don't always have consistent names, pattern-based gitignore rules like *.pem are easy to forget to add — or easy to add after the key is already tracked.

2. Baked into Docker Images and CI Artifacts

CI pipelines often need SSH access to pull from private repositories or deploy over SSH. A common shortcut is mounting or copying the private key into the build context. If that key isn't removed before the final image layer is committed — or if it's removed in a later RUN step rather than in the same layer — it persists in the image history. Anyone with pull access to the registry can extract it.

3. Stored in Dotfiles and Shared Config Repos

Developers who version-control their dotfiles (a common and otherwise sensible practice) sometimes include their ~/.ssh/ directory, or snippets from it, in their dotfile repo. The ~/.ssh/config file itself rarely contains keys, but it can reveal hostnames, usernames, and identity file paths that help an attacker reconstruct access paths.

4. Pasted into Collaboration Tools

A teammate asks "can you share the deploy key for staging so I can test this?" and the key lands in Slack, Notion, or a Jira comment. This is covered more broadly elsewhere, but it's worth calling out specifically for SSH keys because the blast radius is so large and detection in collaboration tools is harder than in code.

5. Left in Backup Archives and Object Storage

Server backups, AMI snapshots, and tarballs of home directories frequently include ~/.ssh/id_rsa or ~/.ssh/id_ed25519. If those archives land in an S3 bucket with overly permissive ACLs — or are synced to a shared drive — the key travels with them.

What a Leaked Deploy Key Can Actually Do

A GitHub or GitLab deploy key with write access lets an attacker push arbitrary code to your repository. Combined with an active CI/CD pipeline, that means arbitrary code execution in your deployment environment. Read-only deploy keys are less immediately dangerous but still expose your entire codebase, including any other secrets embedded in it.

An SSH private key registered on a production server doesn't need elevated privileges to be dangerous. If it grants login access to a bastion host, an attacker can pivot from there. If it's used in an authorized_keys file across a fleet, one key leak becomes a fleet-wide compromise.

How to Check Whether Your SSH Keys Have Already Been Leaked

Search Your Git History

Private keys have recognizable PEM headers. Run the following against your local repo to scan the full commit history:

git log --all --full-history -p \
  | grep -E "BEGIN (RSA|EC|OPENSSH|DSA) PRIVATE KEY"

This is slow on large repos but catches keys committed at any point. For a faster, more comprehensive scan across all your repositories — including forks and archived repos — run a free GhostCred scan, which checks for private key patterns alongside hundreds of other credential types in about 60 seconds.

Audit Registered Deploy Keys

On GitHub, navigate to each repository → Settings → Deploy keys and audit which keys are active. For org-wide visibility, use the GitHub API:

gh api /repos/{owner}/{repo}/keys

Remove any key whose corresponding private key you can't fully account for. If you're unsure, rotate first and investigate after.

Check Your Docker Images

Use docker history --no-trunc <image> to inspect layer commands, and tools like dive to browse the layer filesystem. Look for any layer that adds a file matching *.pem, id_rsa, id_ed25519, or similar patterns.

Remediation: A Prioritized Action Plan

  1. Revoke the key immediately. If you suspect a private key has been exposed, remove it from every authorized_keys file and every registered deploy key location before doing anything else. Access first, cleanup second.
  2. Generate a replacement keypair. Use ed25519 rather than RSA where possible: ssh-keygen -t ed25519 -C "deploy@yourproject" -f ./new_deploy_key. Register only the public key in your target systems.
  3. Purge the key from Git history. Use git filter-repo (preferred over the deprecated git filter-branch) to rewrite history and remove the file. Force-push and notify all collaborators to re-clone.
  4. Add protective .gitignore rules. At the repo root and in your global gitignore (~/.gitignore_global), add: *.pem, *.key, id_rsa, id_ed25519, *.ppk.
  5. Use short-lived credentials where possible. GitHub's OIDC-based authentication for Actions, AWS IAM roles with short token lifetimes, and Vault SSH secrets engine can all issue time-bound credentials that expire automatically — eliminating the long-lived key problem entirely.
  6. Store deploy keys in a secrets manager. If a CI job genuinely needs an SSH key at runtime, inject it from AWS Secrets Manager, HashiCorp Vault, or your platform's native secret store. Never write it to disk in a layer that gets committed.

Prevention: Making Key Leakage Structurally Hard

Pre-Commit Hooks

Tools like pre-commit with detect-secrets or gitleaks as hooks will block commits containing private key headers before they ever reach remote. This is a low-friction, high-value control — add it to your project's .pre-commit-config.yaml and enforce it in onboarding docs.

Principle of Least Privilege for Deploy Keys

Most deploy keys only need read access. Grant write access only to the specific workflow that needs it, and scope it to the minimum number of repositories. Avoid reusing the same keypair across multiple environments or services.

Regular Rotation Policy

Even without a known compromise, rotate deploy keys on a schedule — quarterly is a reasonable starting point for most teams. Document which system or person owns each key so that rotation doesn't stall because nobody knows who registered it.

The Key Insight

SSH private keys and deploy keys sit at an awkward intersection: they feel like infrastructure concerns, so developers don't think of them as "secrets," and they feel like developer concerns, so ops teams don't always scan for them. That gap is exactly where leaks happen. Treat every private key with at least as much care as your most sensitive API token — because in many environments, it grants far broader access than any token would.

See what's exposed in your own code.

Run a free scan