No private keys in repo
On this page
Detect private cryptographic keys committed to the repository. Two detection paths run in parallel:
-
Content scan - match PEM-armored block headers anywhere in any text file. These headers are standardized across OpenSSL, OpenSSH, and GnuPG and have effectively zero false-positive rate.
-
Filename scan - flag file paths that conventionally hold private keys (
id_rsa,*.pem,*.key, …) so binary key formats without a PEM header still get caught.
Once a private key is pushed to a remote, regeneration is the only remedy. Pre-commit is the cheapest place to stop the leak.
BLOCK - private keys, rotate immediately#
| Detection | Source | Examples |
|---|---|---|
-----BEGIN RSA PRIVATE KEY----- |
content | PKCS#1 RSA |
-----BEGIN PRIVATE KEY----- |
content | PKCS#8 unencrypted |
-----BEGIN ENCRYPTED PRIVATE KEY----- |
content | PKCS#8 encrypted |
-----BEGIN OPENSSH PRIVATE KEY----- |
content | OpenSSH format |
-----BEGIN DSA PRIVATE KEY----- |
content | DSA |
-----BEGIN EC PRIVATE KEY----- |
content | elliptic curve |
-----BEGIN PGP PRIVATE KEY BLOCK----- |
content | GnuPG private key |
| SSH private key filename | filename | id_rsa, id_dsa, id_ed25519, id_ecdsa (without .pub suffix) |
| Key/keystore extension | filename | *.pem, *.key, *.p12, *.pfx, *.jks |
The filename .pub suffix is treated as a strong signal that the
file is a PUBLIC key, not a private one (id_rsa.pub → PASS).
INFO - public certificates, catalogued#
| Detection | Source | Why INFO |
|---|---|---|
-----BEGIN CERTIFICATE----- |
content | X.509 certs are public by design; sometimes legitimately committed (CA chains, public server certs in test fixtures). Inventory without blocking. |
.crt, .cer extension |
filename | Same rationale. |
If a commit contains both BLOCK and INFO detections, BLOCK wins; the reason lists both.
Failure message#
❌ security/no-private-keys-in-repo (security)
private keys detected (content redacted):
- keys/server.pem:1 - PEM private key header
- id_rsa:0 - SSH private key filename
fix: remove the key, REGENERATE IT (assume compromised), store
via secret manager / KMS, and add ONLY the corresponding
`.pub` file if a public counterpart is needed in the repo.
INFO output (‘certificates catalogued’) uses non-rotation language.
Override#
Per-file (suppresses every detection in the file):
# appframes:disable security/no-private-keys-in-repo
Per-line (suppresses the line that follows):
# appframes:disable-next-line security/no-private-keys-in-repo
-----BEGIN RSA PRIVATE KEY-----
Use per-line ONLY for genuine test fixtures (e.g. a unit test that
needs a known-bad key to exercise parsing). For systematic
suppression (e.g. a fixtures/ directory of test keys), use a
per-file disable OR add the directory to [scan].exclude in
appframes.toml.
The filename-pattern detection is also suppressed by either marker
when present in the file’s content - but a zero-byte id_rsa can’t
hold a comment, so it’ll fire regardless. That’s intentional: a
literal id_rsa file is unambiguously suspicious.
What’s NOT detected#
Documented so a future “why didn’t it catch X?” has an answer:
- Self-generated random secrets that aren’t PEM-armored and don’t
use a key-suggesting filename. Use the
security/no-hardcoded-credentialsframe for prefix-known tokens; entropy-based detection of arbitrary random strings is V0.6+ territory (false-positive prone on UUIDs / hashes / minified output). - DER-encoded keys without a recognized extension (e.g. a key
saved as
keyfile.bin). DER is binary; we don’t currently parse ASN.1. - JWK (JSON Web Key) format private keys - JSON wrapping with
"d"field. Easy to add in a future revision once we see one in the wild. - Public keys (
-----BEGIN PUBLIC KEY-----,*.pub) - public by definition, no need to surface.
BLOCK rejects the push. Turn frames on per repo on the dashboard's Policy page - see choosing what the gate checks.
Source on GitHub Live demo How it works Questions: contact@nimblegate.com