Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion internal/security/display_redact.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ var displaySecretPatterns = []struct {
// the 4th detection/display drift: the header (and any partial key body
// behind it) rendered raw. Runs AFTER the complete-block pattern, so
// only genuinely unterminated headers reach it; masks to end of buffer.
{"private_key_truncated", regexp.MustCompile(`(?s)-----BEGIN (?:[A-Z ]*)PRIVATE KEY-----.+`), []string{"-----BEGIN"}},
// #3084: gated on a base64-looking body right after the header, so
// prose that merely MENTIONS the literal header (docs / teaching text)
// is no longer masked to end-of-buffer.
{"private_key_truncated", regexp.MustCompile(`(?s)(-----BEGIN (?:[A-Z ]*)PRIVATE KEY-----[\r\n]+[A-Za-z0-9+/=]{20,}.*)`), []string{"-----BEGIN"}},
{"openai_key", regexp.MustCompile(`\b(sk-(?:proj-|svcacct-)?[A-Za-z0-9\-_]{20,})\b`), []string{"sk-"}},
{"anthropic_key", regexp.MustCompile(`\b(sk-ant-[A-Za-z0-9\-_]{70,})\b`), []string{"sk-ant-"}},
{"jwt", regexp.MustCompile(`\b(eyJ[A-Za-z0-9_\-]{10,}\.eyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,})\b`), []string{"eyJ"}},
Expand Down
5 changes: 4 additions & 1 deletion internal/security/secretdetect.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ var secretPatterns = []SecretPattern{
ID: "private_key_block",
Name: "Private Key (PEM)",
Severity: "high",
Pattern: regexp.MustCompile(`-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----`),
// #3084: ENCRYPTED (openssl pkcs8 -topk8 passphrase form) added to
// the prefix alternation so the detection layer stops trailing the
// display layer's [A-Z ]* prefix class for this variant.
Pattern: regexp.MustCompile(`-----BEGIN (?:ENCRYPTED |RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----`),
},
{
ID: "ssh_private_key",
Expand Down
47 changes: 47 additions & 0 deletions internal/security/zz_issue3084_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package security

import (
"strings"
"testing"
)

// #3084 V2: detection layer must flag the ENCRYPTED (passphrase-protected)
// private key form, matching the display layer's coverage.
func TestIssue3084EncryptedPEMDetected(t *testing.T) {
const body = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKYwgg\n-----END ENCRYPTED PRIVATE KEY-----"
findings := ScanForSecrets("config/prod.key", body)
found := false
for _, f := range findings {
if f.PatternID == "private_key_block" {
found = true
}
}
if !found {
t.Errorf("ENCRYPTED PRIVATE KEY not flagged by private_key_block; findings=%v", findings)
}
// plain forms still detected (no regression)
if ScanForSecrets("config/k.key", "-----BEGIN RSA PRIVATE KEY-----\nMIIB\n-----END RSA PRIVATE KEY-----") == nil {
t.Error("RSA PRIVATE KEY regression")
}
}

// #3084 V1: prose that merely MENTIONS the literal header must not be
// masked to end-of-buffer; a genuine truncated key body still is.
func TestIssue3084TruncatedPEMGate(t *testing.T) {
// prose: header followed by ordinary explanation text (no base64 run)
prose := "-----BEGIN PRIVATE KEY-----\nThe PEM format wraps a base64 body between header and footer lines, as documented here."
if out := RedactForDisplay(prose); out != prose {
t.Errorf("prose mention masked: %q", out)
}
// genuine truncated key: header + 20+ base64 chars, no END
truncated := "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASC"
out := RedactForDisplay(truncated)
if strings.Contains(out, "MIIEvQIBADANBgkqhkiG9w0BAQEFAASC") {
t.Errorf("truncated key body leaked: %q", out)
}
// complete block still masked by the original pattern
complete := "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASC\n-----END PRIVATE KEY-----"
if out2 := RedactForDisplay(complete); strings.Contains(out2, "MIIEvQIBADANBgkqhkiG9w0BAQEFAASC") {
t.Errorf("complete block body leaked: %q", out2)
}
}
Loading