diff --git a/internal/redaction/betterleaks.toml b/internal/redaction/betterleaks.toml index a02a0fdec..047af011a 100644 --- a/internal/redaction/betterleaks.toml +++ b/internal/redaction/betterleaks.toml @@ -21,6 +21,17 @@ targetRules = ["generic-api-key"] regexTarget = "match" regexes = ['''(?i)\bsha(?:1|224|256|384|512):[0-9a-f]{40,128}\b'''] +[[allowlists]] +description = "SSH public key fingerprints, such as the output of git log --show-signature" +targetRules = ["generic-api-key"] +regexTarget = "match" +# OpenSSH prints a fingerprint as SHA256: and the unpadded base64 of the hash, +# which is always 43 characters. The fingerprint must end the match, so a +# longer value after SHA256:, also one that goes on after a - . or _, is still +# redacted. Only the terminator the rule itself adds to its match can follow: +# a quote, a backtick, whitespace, a semicolon, or an escaped line break. +regexes = ['''\bSHA256:[A-Za-z0-9+/]{43}(?:[\x60'"\s;]|\\[nr])?$'''] + [[allowlists]] description = "Code expressions, test durations and the bare key word, which are not password values" targetRules = ["generic-password"] diff --git a/internal/redaction/precision_test.go b/internal/redaction/precision_test.go index 06a99169a..4fd340ca7 100644 --- a/internal/redaction/precision_test.go +++ b/internal/redaction/precision_test.go @@ -36,6 +36,9 @@ const ( // sampleAPIKey is a random-looking key the generic-api-key rule matches. sampleAPIKey = "q8Zr2LmX7v" + "Pk4TnW9sYb" sampleEmail = "sarah@chainloop.local" + // sampleSSHFingerprint is the unpadded base64 of a SHA-256 hash, as + // OpenSSH prints the fingerprint of a public key after SHA256: + sampleSSHFingerprint = "q3Rk8vLm2+Zx7Pw4" + "Tn9sYbHc1/Fd6Gj0" + "Ue5Ai8Ko2Ws" ruleGenericAPIKey = "generic-api-key" ruleGitHubPAT = "github-pat" @@ -148,6 +151,17 @@ func TestRulesConfigKeepsNonSecrets(t *testing.T) { text: "API_URL=http://localhost:48002\nCAS_ENDPOINT=localhost:49001\nTEST_EMAIL=" + sampleEmail + "\n", wantDefaultRule: ruleCredentialURI, }, + { + // git log --show-signature on a commit signed with an SSH key + name: "ssh key fingerprint of a commit signature", + text: "Good \"git\" signature for " + sampleEmail + " with ED25519 key SHA256:" + sampleSSHFingerprint + "\n", + wantDefaultRule: ruleGenericAPIKey, + }, + { + name: "rsa key fingerprint of a commit signature", + text: "Good \"git\" signature for " + sampleEmail + " with RSA key SHA256:" + sampleSSHFingerprint + "\n", + wantDefaultRule: ruleGenericAPIKey, + }, } for _, tc := range testCases { @@ -191,6 +205,31 @@ func TestRulesConfigStillRedactsSecrets(t *testing.T) { secret: sampleDigest, wantRule: ruleGenericAPIKey, }, + { + name: "base64 key without a fingerprint prefix", + text: "api_key: " + sampleSSHFingerprint + "\n", + secret: sampleSSHFingerprint, + wantRule: ruleGenericAPIKey, + }, + { + name: "key longer than a fingerprint after a SHA256 prefix", + text: "signing key SHA256:" + strings.Repeat(sampleAPIKey, 3) + "\n", + secret: strings.Repeat(sampleAPIKey, 3), + wantRule: ruleGenericAPIKey, + }, + { + name: "key that continues a fingerprint with a separator", + text: "signing key SHA256:" + strings.Repeat(sampleAPIKey, 3)[:43] + "-" + sampleAPIKey + "\n", + secret: sampleAPIKey, + wantRule: ruleGenericAPIKey, + }, + { + name: "api key on the line after an ssh key fingerprint", + text: "Good \"git\" signature with ED25519 key SHA256:" + sampleSSHFingerprint + "\napi_key: " + sampleAPIKey + "\n", + secret: sampleAPIKey, + wantRule: ruleGenericAPIKey, + keep: "SHA256:" + sampleSSHFingerprint, + }, { name: "password next to an email username", text: "host: db.internal\nuser: " + sampleEmail + "\npassword: " + samplePassword + "\n",