From 67d0241853a5a8e55e2166868287c1f148176939 Mon Sep 17 00:00:00 2001 From: genisis0x Date: Wed, 29 Jul 2026 12:07:34 +0530 Subject: [PATCH] fix(detectors/privatekey): report encrypted keys with uncrackable passphrases (#5127) When a private key is passphrase-protected and Crack cannot recover the passphrase from the built-in wordlist, FromData set a verification error on the result but then continued past the append, dropping the finding entirely. An encrypted key committed to a repository is still a real exposure (offline-crackable, weak or legacy KDFs, and the passphrase is often reused or committed nearby), so it should surface as an unverified finding. Append the result before continuing so the encrypted key is reported as unverified with its verification error and encrypted:true metadata, matching how unencrypted keys are already reported under --no-verification. Closes #5115 Co-authored-by: Shahzad Haider <76992801+shahzadhaider1@users.noreply.github.com> --- pkg/detectors/privatekey/privatekey.go | 6 +++ pkg/detectors/privatekey/privatekey_test.go | 41 +++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/pkg/detectors/privatekey/privatekey.go b/pkg/detectors/privatekey/privatekey.go index c65d57925..d7ea58826 100644 --- a/pkg/detectors/privatekey/privatekey.go +++ b/pkg/detectors/privatekey/privatekey.go @@ -81,7 +81,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result s1.ExtraData["encrypted"] = "true" parsedKey, passphrase, err = Crack([]byte(token)) if err != nil { + // The key is encrypted and the passphrase could not be + // recovered from the wordlist. It still represents a real + // exposure (offline-crackable, weak/legacy KDFs, passphrase + // often reused or committed nearby), so surface it as an + // unverified finding instead of dropping it silently. s1.SetVerificationError(err, token) + results = append(results, s1) continue } if passphrase != "" { diff --git a/pkg/detectors/privatekey/privatekey_test.go b/pkg/detectors/privatekey/privatekey_test.go index 661a8fd96..c5f5b76aa 100644 --- a/pkg/detectors/privatekey/privatekey_test.go +++ b/pkg/detectors/privatekey/privatekey_test.go @@ -96,3 +96,44 @@ func TestPrivatekey_Pattern(t *testing.T) { }) } } + +// encryptedUncrackablePattern is an ed25519 key protected by a passphrase that +// is not present in the built-in wordlist (pkg/detectors/privatekey/list.txt), +// so Crack cannot recover it. +var encryptedUncrackablePattern = `-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDnvQyInG +vB+yh/WYczTGXbAAAAGAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILUSv84lMSLVN0iP +knFlHoYobo1oohtLNp/ihBaf76QxAAAAoErDjg2HQs1hgYm0vfyrpBxWrcJ1u/LQG4o6cm +GJ3NnZmMmJemrnMXjGHB3zj63AEPxxyb6BWZH5Olb5gYMgWcnOU91cGvyC7aT6C5cOnFb1 +ZtkdxTxqeUOMFS51gwDT04aURSz/caCV9KN4Y2MCHyW+GWxxD7eL0R6KhyT2j0z2BewCw9 +kRN1fq3C2rTb+wTaNz8q9X0R+6JGxfXGhpTJ0= +-----END OPENSSH PRIVATE KEY----- +` + +// TestPrivatekey_EncryptedKeyReported asserts that a passphrase-protected key +// whose passphrase cannot be cracked is still surfaced as an unverified finding +// rather than being dropped silently (issue #5115). +func TestPrivatekey_EncryptedKeyReported(t *testing.T) { + d := Scanner{} + input := fmt.Sprintf("%s = '%s'", keyword, encryptedUncrackablePattern) + + results, err := d.FromData(context.Background(), false, []byte(input)) + if err != nil { + t.Fatalf("FromData error = %v", err) + } + + if len(results) != 1 { + t.Fatalf("expected 1 result for an encrypted key with an uncrackable passphrase, got %d", len(results)) + } + + r := results[0] + if r.Verified { + t.Errorf("expected the finding to be unverified") + } + if r.VerificationError() == nil { + t.Errorf("expected a verification error to be set on the finding") + } + if r.ExtraData["encrypted"] != "true" { + t.Errorf("expected ExtraData[\"encrypted\"] = \"true\", got %q", r.ExtraData["encrypted"]) + } +}